diff -urN exim-4.41-orig/OS/Makefile-Base exim-4.41/OS/Makefile-Base --- exim-4.41-orig/OS/Makefile-Base Thu Jul 22 10:46:51 2004 +++ exim-4.41/OS/Makefile-Base Thu Jul 22 16:31:13 2004 @@ -285,14 +285,14 @@ # Targets for final binaries; the main one has a build number which is # updated each time. We don't bother with that for the auxiliaries. -OBJ_EXIM = acl.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \ +OBJ_EXIM = acl.o bmi_spam.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o demime.o \ directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \ filtertest.o globals.o \ - header.o host.o ip.o log.o lss.o match.o moan.o \ + header.o host.o ip.o log.o lss.o malware.o match.o mime.o moan.o \ os.o parse.o queue.o \ - rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \ - route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \ - store.o string.o tls.o tod.o transport.o tree.o verify.o \ + rda.o readconf.o receive.o regex.o retry.o rewrite.o rfc2047.o \ + route.o search.o sieve.o smtp_in.o smtp_out.o spam.o spf.o spool_in.o spool_mbox.o spool_out.o \ + srs.o store.o string.o tls.o tnef.o tod.o transport.o tree.o verify.o \ local_scan.o $(EXIM_PERL) exim: pcre/libpcre.a lookups/lookups.a auths/auths.a \ @@ -498,12 +498,14 @@ # Dependencies for the "ordinary" exim modules acl.o: $(HDRS) acl.c +bmi_spam.o: $(HDRS) bmi_spam.c child.o: $(HDRS) child.c crypt16.o: $(HDRS) crypt16.c daemon.o: $(HDRS) daemon.c dbfn.o: $(HDRS) dbfn.c debug.o: $(HDRS) debug.c deliver.o: $(HDRS) deliver.c +demime.o: $(HDRS) demime.c directory.o: $(HDRS) directory.c dns.o: $(HDRS) dns.c enq.o: $(HDRS) enq.c @@ -517,7 +519,9 @@ ip.o: $(HDRS) ip.c log.o: $(HDRS) log.c lss.o: $(HDRS) lss.c +malware.o: $(HDRS) malware.c match.o: $(HDRS) match.c +mime.o: $(HDRS) mime.c moan.o: $(HDRS) moan.c os.o: $(HDRS) os.c parse.o: $(HDRS) parse.c @@ -525,6 +529,7 @@ rda.o: $(HDRS) rda.c readconf.o: $(HDRS) readconf.c receive.o: $(HDRS) receive.c +regex.o: $(HDRS) regex.c retry.o: $(HDRS) retry.c rewrite.o: $(HDRS) rewrite.c rfc2047.o: $(HDRS) rfc2047.c @@ -533,11 +538,16 @@ sieve.o: $(HDRS) sieve.c smtp_in.o: $(HDRS) smtp_in.c smtp_out.o: $(HDRS) smtp_out.c +spam.o: $(HDRS) spam.c +spf.o: $(HDRS) spf.c spool_in.o: $(HDRS) spool_in.c +spool_mbox.o: $(HDRS) spool_mbox.c spool_out.o: $(HDRS) spool_out.c +srs.o: $(HDRS) srs.c store.o: $(HDRS) store.c string.o: $(HDRS) string.c tls.o: $(HDRS) tls.c tls-gnu.c tls-openssl.c +tnef.o: $(HDRS) tnef.c tod.o: $(HDRS) tod.c transport.o: $(HDRS) transport.c tree.o: $(HDRS) tree.c diff -urN exim-4.41-orig/README.EXISCAN exim-4.41/README.EXISCAN --- exim-4.41-orig/README.EXISCAN Thu Jan 1 01:00:00 1970 +++ exim-4.41/README.EXISCAN Thu Jul 22 16:31:13 2004 @@ -0,0 +1 @@ +Please refer to doc/exiscan-acl-spec.txt diff -urN exim-4.41-orig/doc/exiscan-acl-examples.txt exim-4.41/doc/exiscan-acl-examples.txt --- exim-4.41-orig/doc/exiscan-acl-examples.txt Thu Jan 1 01:00:00 1970 +++ exim-4.41/doc/exiscan-acl-examples.txt Thu Jul 22 16:31:13 2004 @@ -0,0 +1,440 @@ +-------------------------------------------------------------- +exiscan-acl example configurations / FAQ +-------------------------------------------------------------- + +Author: Tom Kistner + +The exiscan website is at http://duncanthrax.net/exiscan/. You +will find the latest patch versions, as well as links to the +mailing list and its archives there. + +This document shows some example configuration snippets: + +1. Basic sitewide virus and spam filtering by rejecting + matching messages after DATA. +2. Adding a cryptographic "checks done" header that will + prevent re-scanning when the message re-visits one of your + mail servers, and the body size did not change. +3. Marking spam-suspicious messages with extra headers and a + tag in the subject. +4. Having more than one spam threshold to act on. +5. Redirecting matching messages to special accounts while + preserving envelope recipient information. +6. A multi-profile configuration for sites where different + "customers" (or users) have different content scanning + preferences. + +These examples serve as a guideline and should give you some +pointers that can help you to create your own configuration. +Please do not copy these examples verbatim. You really need to +know what you are doing. The content scanning topic is really +complex and you can screw up your mail server easily if you do +not get it "right". + +I recommend to read the exiscan documentation on the above +mentioned website before trying to make sense of the following +examples. + +Each example shows part of a DATA ACL definition, unless +otherwise noted. + +-------------------------------------------------------------- +1. Basic setup for simple site-wide filtering +-------------------------------------------------------------- +The following example only shows the most basic use of the +exiscan content filtering features. You should see it as a +base that you can build on. However, it may be all you need +for smaller systems with only a few users. + +/* ----------------- +# Do not scan messages submitted from our own hosts +# and locally submitted messages. Since the DATA ACL +# is not called for messages not submitted via SMTP +# protocols, we do not need to check for an empty +# host field. +accept hosts = 127.0.0.1:+relay_from_hosts + +# Unpack MIME containers and reject file extensions +# used by worms. Note that the extension list may be +# incomplete. +deny message = $found_extension files are not accepted here + demime = com:vbs:bat:pif:scr + +# Reject messages that have serious MIME errors. +# This calls the demime condition again, but it +# will return cached results. +deny message = Serious MIME defect detected ($demime_reason) + demime = * + condition = ${if >{$demime_errorlevel}{2}{1}{0}} + +# Reject messages containing malware. +deny message = This message contains malware ($malware_name) + malware = * + +# Reject spam messages. Remember to tweak your +# site-wide SA profile. Do not spam-scan messages +# larger than eighty kilobytes. +deny message = Classified as spam (score $spam_score) + condition = ${if <{$message_size}{80k}{1}{0}} + spam = nobody + +# Finally accept all other messages that have +# made it to this point +accept +------------------ */ + + + +-------------------------------------------------------------- +2. Adding a cryptographic "scanning done" header +-------------------------------------------------------------- + +If you have a mail setup where the same message may pass your +server twice (redirects from other servers), or you have +multiple mail servers, you may want to make sure that each +message is only checked once, to save processing time. Here is +how to do it: + +At the very beginning of your DATA ACL, put this: + +/* ----------------- +# Check our crytographic header. If it matches, accept +# the message. +accept condition = ${if eq {${hmac{md5}\ + {mysecret}\ + {$body_linecount}}}\ + {$h_X-Scan-Signature:} {1}{0}} +------------------ */ + +At the end, just before the final "accept" verb, put this: + +/* ----------------- +# Add the cryptographic header. +warn message = X-Scan-Signature: ${hmac{md5}{mysecret}\ + {$body_linecount}} +------------------ */ + +Notice the two "mysecret" strings? Replace them with your own +secret, and don't tell anyone :) The hash also includes the +number of lines in the message body, to protect against +message "modifications". + + +-------------------------------------------------------------- +3. Marking Spam messages with extra headers and subject tag +-------------------------------------------------------------- + +Since the false positive rate with spam scanning is high +compared to virus scanning, it is wise to implement a scheme +with two thresholds, where you reject messages with high +scores and just mark messages with lower scores. End users can +then set up filters in their Mail User Agents (MUAs). Since +many MUAs can not filter on custom headers, it can be +necessary to put a "spam tag" in the subject line. Since it is +not (yet) possible to remove headers in Exims DATA ACL, we +must do this in a system filter. Please see the Exim docs on +how to set up a system filter. + +The following example will unconditionally put two spam +information headers in each message, if it is smaller than +eighty kilobytes: + +/* ----------------- +# Always put X-Spam-Score header in the message. +# It looks like this: +# X-Spam-Score: 6.6 (++++++) +# When a MUA cannot match numbers, it can match for an +# equivalent number of '+' signs. +# The 'true' makes sure that the header is always put +# in, no matter what the score. +warn message = X-Spam-Score: $spam_score ($spam_bar) + condition = ${if <{$message_size}{80k}{1}{0}} + spam = nobody:true + +# Always put X-Spam-Report header in the message. +# This is a multiline header that informs the user +# which tests a message has "hit", and how much a +# test has contributed to the score. +warn message = X-Spam-Report: $spam_report + condition = ${if <{$message_size}{80k}{1}{0}} + spam = nobody:true +------------------ */ + +For the subject tag, we prepare a new subject header in the +ACL, then swap it with the original Subject in the system +filter. + +In the DATA ACL, put this: +/* ----------------- +warn message = X-New-Subject: *SPAM* $h_subject: + spam = nobody +------------------ */ + +In the system filter, put this: +/* ----------------- +if "${if def:header_X-New-Subject: {there}}" is there +then + headers remove Subject + headers add "Subject: $h_X-New-Subject:" + headers remove X-New-Subject +endif +------------------ */ + + +-------------------------------------------------------------- +4. Defining multiple spam thresholds with different actions +-------------------------------------------------------------- +If you want to mark messages if they exceed your threshold, +but also have a higher "cutoff" threshold where you reject +messages, use the example above, plus this part: + +/* ----------------- +deny message = Spam score too high ($spam_score) + condition = ${if <{$message_size}{80k}{1}{0}} + spam = nobody:true + condition = ${if >{$spam_score_int}{100}{1}{0}} +------------------ */ + +The last condition is only true if the spam score exceeds 10.0 +points (Keep in mind that $spam_score_int is the messages +score multiplied by ten). + + + +-------------------------------------------------------------- +5. Redirect infected or spam messages to special accounts +-------------------------------------------------------------- +Sometimes it is desirable not to reject messages, but to stop +them for inspection, and then decide wether to delete, bounce +or pass them. + +There are multiple ways to achieve this. The simplest way is +to freeze suspicious messages, and then thaw or bounce them +after a review. Here is a simple example that will freeze spam +suspicious messages when they exceed the SA threshold: + +/* ----------------- +warn log_message = frozen by spam scanner, score $spam_score + spam = nobody + control = freeze +------------------ */ + +Another way is to redirect suspicious messages to special +postmaster accounts, where they can be reviewed. This involves +setting up a router for these special accounts that acts on a +header set in the DATA ACL. + +This is the DATA ACL entry: + +/* ----------------- +warn message = X-Redirect-To: spambox@mycompany.com + spam = nobody +------------------ */ + +This puts the target address in a special header, which can in +turn be read with this router: + +/* ----------------- +scan_redirect: + driver = redirect + condition = ${if def:h_X-Redirect-To: {1}{0}} + headers_add = X-Original-Recipient: $local_part@$domain + data = $h_X-Redirect-To: + headers_remove = X-Redirect-To + redirect_router = my_second_router +------------------ */ + +This router should probably be your very first one, and you +need to edit the last line (redirect_router = ) to replace +"my_second_router" with the name of your original first +router. Note that the original message recipient is saved in +the "X-Original-Recipient" header, and the X-Redirect-To +header line is removed. + + +-------------------------------------------------------------- +6. Having multiple content scanning profiles for several + users or domains. +-------------------------------------------------------------- +This is one of the most often asked questions, and it also has +the most complicated answer. To understand the difficulties, +you should first remember that the exiscan facilities are run +in the DATA ACL. This ACL is called ONCE per message, after +the sending server has transmitted the end-of-data marker. +This gives us the very cool possibility to reject unwanted +messages with a 5xx error code in response. The big drawback +is that a message can have multiple recipients, and you can +only reject or accept a message for ALL recipients, not +individual ones. + +I will first sum up the possible solutions to this dilemma: + + a. Make sure that each incoming message can have only one + envelope recipient. This is brutal, but effective and + reliably solves the problem on your end. :) Drawback: + Incoming mail to multiple recipients is slowed down. The + exact time depends on the retry strategies of the sending + hosts. + + b. Offer a limited number of "profiles" that your customers + can subscribe to. Then, similar to a.), only accept + recipients with the same profile in a single "batch", and + defer the others. This does improve on the drawback of + a.) a bit. + + c. Do scanning as usual, but never reject messages in the + DATA ACL. Instead put appropriate information in extra + headers and query those in routers or transports later. + Drawback: You'll have to send bounces yourself, and your + queue will fill up with frozen bounces. Advantage: clean + solution, protocol-wise. + +As you see, you can't have your cake and eat it too. Now lets +get into the details of each possible solution. + +a.) Making sure each incoming message that will be scanned + only has one recipient. + + To use this scheme, you must make sure that you do not use + it on your +relay_from_hosts and authenticated senders. + Both of these may be MUAs who cannot cope with such a + thing. + + Here is a RCPT ACL that implements the behaviour + (shortened, do not copy 1:1!): + + /* ------------ + acl_check_rcpt: + + # accept local, relay-allowed + # and authenticated sources + + accept hosts = : + deny local_parts = ^.*[@%!/|] + accept hosts = 127.0.0.1:+relay_from_hosts + accept authenticated = * + + # the following treat non-local, + # non-authenticated sources + + defer message = only one recipient at a time + condition = ${if def:acl_m0 {1}{0}} + + # [ .. ] + # put RBLs etc. here + # [ .. ] + + accept domains = +local_domains + endpass + message = unknown user + verify = recipient + set acl_m0 = $local_part@$domain + + accept domains = +relay_to_domains + endpass + message = unrouteable address + verify = recipient + set acl_m0 = $domain + + deny message = relay not permitted + ------------ */ + + The lines which contain acl_m0 are the important ones. The + $acl_m0 variable gets set when a remote server + successfully sends one RCPT. Subsequent RCPT commands are + deferred if this variable is set. The $acl_m0 variable now + contains the single recipient domain, which you can use in + the DATA ACL to determine the scanning profile. + + This scheme is only recommended for small servers with a + low number of possible recipients, where recipients do not + belong to the same organization. An example would be a + multiuser shell server. + + +b.) Having several scanning profiles that "customers" can + choose from. + + Suppose you want to offer three profiles. Lets call them + "reject-aggressive", "reject-conservative", and "warn + -only". Customers can select one of the profiles for each + of their domains. So you end up with a mapping like this: + + domain-a.com: reject-aggressive + domain-b.org: warn-only + domain-c.net: reject-aggressive + domain-d.com: reject-conservative + [ .. ] + + Suppose you put that in a file called /etc/exim/scanprefs + + Now we make a scheme similar to a.), but we do allow more + than one recipient if they have the same scanning profile + than the first recipient. + + Here is a RCPT ACL that implements the behaviour + (shortened, do not copy 1:1!): + + /* ------------ + acl_check_rcpt: + + # accept local, relay-allowed and authenticated sources + + accept hosts = : + deny local_parts = ^.*[@%!/|] + accept hosts = 127.0.0.1:+relay_from_hosts + accept authenticated = * + + # the following treat non-local, non-authenticated sources + + defer message = try this address in the next batch + condition = ${if eq {${acl_m0}}\ + {${lookup{$domain}\ + lsearch{/etc/exim/scanprefs}}}\ + {0}{1}} + + # [ .. ] + # put RBLs etc. here + # [ .. ] + + accept domains = +local_domains + endpass + message = unknown user + verify = recipient + set acl_m0 = $local_part@$domain + + accept domains = +relay_to_domains + endpass + message = unrouteable address + verify = recipient + set acl_m0 = ${lookup{$domain}\ + lsearch{/etc/exim/scanprefs}} + + deny message = relay not permitted + ------------ */ + + Now a recipient address get deferred if its scan profile + does not match the current batch profile. The $acl_m0 + variable contains the name of the profile, that can be + used for processing in the DATA ACL. + + This scheme works pretty well if you keep the number of + possible profiles low, since that will prevent + fragmentation of RCPT blocks. + + +c.) Classic content scanning without the possibility of + rejects after DATA. + + This emulates the "classic" content scanning in routers + and transports. The difference is that we still do the + scan in the DATA ACL, but put the outcome of each facility + in message headers, that can the be evaluated in special + routers, individually for each recipient. + + A special approach can be taken for spam scanning, since + the $spam_score_int variable is also available in routers + and transports (it gets written to the spool files), so + you do not need to put that information in a header, but + rather act on $spam_score_int directly. + diff -urN exim-4.41-orig/doc/exiscan-acl-spec.txt exim-4.41/doc/exiscan-acl-spec.txt --- exim-4.41-orig/doc/exiscan-acl-spec.txt Thu Jan 1 01:00:00 1970 +++ exim-4.41/doc/exiscan-acl-spec.txt Wed Aug 18 16:48:03 2004 @@ -0,0 +1,1283 @@ +-------------------------------------------------------------- +The exiscan-acl patch for exim4 - Documentation +-------------------------------------------------------------- +(c) Tom Kistner 2003-???? +License: GPL + +The exiscan-acl patch adds content scanning to the exim4 ACL +system. It supports the following scanning features: + + - MIME ACL that is called for all MIME parts in + incoming MIME messages. + - Antivirus using 3rd party scanners. + - Anti-spam using SpamAssassin. + - Anti-spam using Brightmail Antispam. + - SPF support via the libspf2 library + - SRS support via the libsrs_alt library + - Regular expression match against headers, bodies, raw + MIME parts and decoded MIME parts. + +These features are hooked into exim by extending exim's ACL +system. The patch adds expansion variables and ACL conditions. +These conditions are designed to be used in the acl_smtp_data +ACL. It is run when the sending host has completed the DATA +phase and is waiting for our final response to his end-of-data +marker. This allows us to reject messages containing +unwanted content at that stage. + +Support for Brightmail AntiSpam requires special compile-time +flags. Please refer to chapter 7 for details. + +The default exim configure file contains commented +configuration examples for some features of exiscan-acl. + + +0. Overall concept / Overview +-------------------------------------------------------------- + +The exiscan-acl patch extends Exims with mechanisms to +deal with the message body content. Most of these additions +affect the ACL system. The exiscan patch adds + +- A new ACL, called 'acl_smtp_mime' (Please see detailed + chapter on this one below). +- ACL conditions and modifiers + o malware (attach 3rd party virus/malware scanner) + o spam (attach SpamAssassin) + o regex (match regex against message, linewise) + o decode (decode MIME part to disk) + o mime_regex (match regex against decoded MIME part) + o control = fakereject (reject but really accept a message) + o spf (do SPF checks) +- expansion variables + (see chapters below for names and explanations) +- configuration options in section 1 of Exim's configure file. + o av_scanner (type and options of the AV scanner) + o spamd_address (network address / socket of spamd daemon). + +All facilites work on a MBOX copy of the message that is +temporarily spooled up in a file called: + + /scan//.eml + +The .eml extension is a friendly hint to virus scanners that +they can expect an MBOX-like structure inside that file. The +file is only spooled up once, when the first exiscan facility +is called. Subsequent calls to exiscan conditions will just +open the file again. The directory is recursively removed +when the acl_smtp_data has finished running. When the MIME +ACL decodes files, they will be put into that same folder by +default. + + +1. The acl_smtp_mime MIME ACL +-------------------------------------------------------------- + +Note: if you are not familiar with exims ACL system, please go +read the documentation on it, otherwise this chapter will not +make much sense to you. + +Here are the facts on acl_smtp_mime: + + - It is called once for each MIME part of a message, + including multipart types, in the sequence of their + position in the message. + + - It is called just before the acl_smtp_data ACL. They share + a result code (the one assed to the remote system after + DATA). When a call to acl_smtp_mime does not yield + "accept", ACL processing is aborted and the respective + result code is sent to the remote mailer. This means that + the acl_smtp_data is NOT called any more. + + - It is ONLY called if the message has a MIME-Version header. + + - MIME parts will NOT be dumped to disk by default, you have + to call the "decode" condition to do that (see further + below). + + - For RFC822 attachments (these are messages attached to + messages, with a content-type of 'message/rfc822'), + the ACL is called again in the same manner as + for the "primary" message, only that the $mime_is_rfc822 + expansion variable is set (see below). These messages + are always decoded to disk before being checked, but + the files are unlinked once the check is done. + +To activate acl_smtp_mime, you need to add assign it the name +of an ACL entry in section 1 of the config file, and then +write that ACL in the ACL section, like: + + /* --------------- + + # -- section 1 ---- + [ ... ] + acl_smtp_mime = my_mime_acl + [ ... ] + + # -- acl section ---- + begin acl + + [ ... ] + + my_mime_acl: + + < ACL logic > + + [ ... ] + + ---------------- */ + +The following list describes all expansion variables that are +available in the MIME ACL: + + $mime_content_type + ------------------ + A very important variable. If the MIME part has a "Content + -Type:" header, this variable will contain its value, + lowercased, and WITHOUT any options (like "name" or + "charset", see below for these). Here are some examples of + popular MIME types, as they may appear in this variable: + + text/plain + text/html + application/octet-stream + image/jpeg + audio/midi + + If the MIME part has no "Content-Type:" header, this + variable is the empty string. + + + $mime_filename + -------------- + Another important variable, possibly the most important one. + It contains a proposed filename for an attachment, if one + was found in either the "Content-Type:" or "Content + -Disposition" headers. The filename will be RFC2047 + decoded, however NO additional sanity checks are done. See + instructions on "decode" further below. If no filename was + found, this variable is the empty string. + + + $mime_charset + ------------- + Contains the charset identifier, if one was found in the + "Content-Type:" header. Examples for charset identifiers are + + us-ascii + gb2312 (Chinese) + iso-8859-1 + + Please note that this value will NOT be normalized, so you + should do matches case-insensitively. + + + $mime_boundary + -------------- + If the current part is a multipart (see $mime_is_multipart) + below, it SHOULD have a boundary string. It is stored in + this variable. If the current part has no boundary parameter + in the "Content-Type:" header, this variable contains the + empty string. + + + $mime_content_disposition + ------------------------- + Contains the normalized content of the "Content + -Disposition:" header. You can expect strings like + "attachment" or "inline" here. + + + $mime_content_transfer_encoding + ------------------------------- + Contains the normalized content of the "Content + -transfer-encoding:" header. This is a symbolic name for + an encoding type. Typical values are "base64" and "quoted + -printable". + + + $mime_content_id + ---------------- + Contains the normalized content of the "Content + -ID:" header. This is a unique ID that can be used to + reference a part from another part. + + + $mime_content_description + ------------------------- + Contains the normalized content of the "Content + -Description:" header. It can contain a human-readable + description of the parts content. Some implementations will + repeat the filename for attachments here, but they are + usually only used for display purposes. + + + $mime_part_count + ---------------- + This is a counter that is raised for each processed MIME + part. It starts at zero for the very first part (which is + usually a multipart). The counter is per-message, so it is + reset when processing RFC822 attachments (see + $mime_is_rfc822). The counter stays set after acl_smtp_mime + is complete, so you can use it in the DATA ACL to determine + the number of MIME parts of a message. For non-MIME + messages, this variable will contain the value -1. + + + $mime_is_multipart + ------------------ + A "helper" flag that is true (1) when the current + part has the main type "multipart", for example + "multipart/alternative" or "multipart/mixed". Since + multipart entities only serve as containers for other parts, + you may not want to carry out specific actions on them. + + + $mime_is_coverletter + -------------------- + This flag attempts to differentiate the "cover letter" of an + e-mail from attached data. It can be used to clamp down on + "flashy" or unneccessarily encoded content in the + coverletter, while not restricting attachments at all. + + It returns 1 (true) for a MIME part believed to be part of + the coverletter, 0 (false) for an attachment. At + present, the algorithm is as follows: + + 1. The outermost MIME part of a message always coverletter. + 2. If a multipart/alternative or multipart/related MIME part + is coverletter, so are all MIME subparts within that + multipart. + 3. If any other multipart is coverletter, the first subpart + is coverletter, the rest are attachments. + 4. All parts contained within an attachment multipart are + attachments. + + As an example, the following will ban "HTML mail" (including + that sent with alternative plain text), while allowing HTML + files to be attached: + + /* ---------------------- + deny message = HTML mail is not accepted here + condition = $mime_is_coverletter + condition = ${if eq{$mime_content_type}{text/html}{1}{0}} + ----------------------- */ + + + $mime_is_rfc822 + --------------- + This flag is true (1) if the current part is NOT a part of + the checked message itself, but part of an attached message. + Attached message decoding is fully recursive. + + + $mime_decoded_filename + ---------------------- + This variable is only set after the "decode" condition (see + below) has been successfully run. It contains the full path + and file name of the file containing the decoded data. + + + $mime_content_size + ------------------ + This variable is only set after the "decode" condition (see + below) has been successfully run. It contains the size of + the decoded part in kilobytes(!). The size is always + rounded up to full kilobytes, so only a completely empty + part will have a mime_content_size of 0. + + +The expansion variables only reflect the content of the MIME +headers for each part. To actually decode the part to disk, +you can use the "decode" condition. The general syntax is + +decode = [//] + +The right hand side is expanded before use. After expansion, +the value can + + - be '0' or 'false', in which case no decoding is done. + - be the string 'default'. In that case, the file will be + put in the temporary "default" directory + /scan// + with a sequential file name, consisting of the message id + and a sequence number. The full path and name is available + in $mime_decoded_filename after decoding. + - start with a slash. If the full name is an existing + directory, it will be used as a replacement for the + "default" directory. The filename will then also be + sequentially assigned. If the name does not exist, it will + be used as the full path and file name. + - not start with a slash. It will then be used as the + filename, and the default path will be used. + +You can easily decode a file with its original, proposed +filename using "decode = $mime_filename". However, you should +keep in mind that $mime_filename might contain anything. If +you place files outside of the default path, they will not be +automatically unlinked. + +The MIME ACL also supports the regex= and mime_regex= +conditions. You can use those to match regular expressions +against raw and decoded MIME parts, respectively. Read the +next section for more information on these conditions. + + + +2. Match message or MIME parts against regular expressions +-------------------------------------------------------------- + +The "regex" condition takes one or more regular expressions as +arguments and matches them against the full message (when +called in the DATA ACL) or a raw MIME part (when called in the +MIME ACL). The "regex" condition matches linewise, with a +maximum line length of 32k characters. That means you can't +have multiline matches with the "regex" condition. + +The "mime_regex" can only be called in the MIME ACL. It +matches up to 32k of decoded content (the whole content at +once, not linewise). If the part has not been decoded with the +"decode" condition earlier in the ACL, it is decoded +automatically when "mime_regex" is executed (using default +path and filename values). If the decoded data is larger +than 32k, only the first 32k characters will be +matched. + +The regular expressions are passed as a colon-separated list. +To include a literal colon, you must double it. Since the +whole right-hand side string is expanded before being used, +you must also escape dollar ($) signs with backslashes. + +Here is a simple example: + +/* ---------------------- +deny message = contains blacklisted regex ($regex_match_string) + regex = [Mm]ortgage : URGENT BUSINESS PROPOSAL +----------------------- */ + +The conditions returns true if one of the regular +expressions has matched. The $regex_match_string expansion +variable is then set up and contains the matching regular +expression. + +Warning: With large messages, these conditions can be fairly +CPU-intensive. + + + +3. Antispam measures with SpamAssassin +-------------------------------------------------------------- + +The "spam" ACL condition calls SpamAssassin's "spamd" daemon +to get a spam-score and a report for the message. You must +first install SpamAssassin. You can get it +at http://www.spamassassin.org, or, if you have a working +Perl installation, you can use CPAN by calling + +perl -MCPAN -e 'install Mail::SpamAssassin' + +SpamAssassin has its own set of configuration files. Please +review its documentation to see how you can tweak it. The +default installation should work nicely, however. + +After having installed and configured SpamAssassin, start the +"spamd" daemon. By default, it listens on 127.0.0.1, TCP port +783. If you use another host or port for spamd, you must set +the spamd_address option in Section 1 of the exim +configuration as follows (example): + +spamd_address = 127.0.0.1 783 + +As of version 2.60, spamd also supports communication over UNIX +sockets. If you want to use these, supply spamd_address with +an absolute file name instead of a address/port pair, like: + +spamd_address = /var/run/spamd_socket + +If you use the above mentioned default, you do NOT need to set +this option. + +You can also have multiple spamd servers to improve +scalability. These can also reside on other hardware reachable +over the network. To specify multiple spamd servers, just put +multiple address/port pairs in the spamd_address option, +separated with colons: + +spamd_address = 192.168.2.10 783 : 192.168.2.11 783 : 192.168.2.12 783 + +Up to 32 spamd servers are supported. The servers will be +queried in a random fashion. When a server fails to respond +to the connection attempt, all other servers are tried +until one succeeds. If no server responds, the "spam" +condition will return DEFER. IMPORTANT: It is not possible +to use the UNIX socket connection method with +multiple spamd servers. + +To use the antispam facility, put the "spam" condition in a +DATA ACL block. Here is a very simple example: + +/* --------------- +deny message = This message was classified as SPAM + spam = joe +---------------- */ + +On the right-hand side of the spam condition, you can put the +username that SpamAssassin should scan for. That allows you to +use per-domain or per-user antispam profiles. The right-hand +side is expanded before being used, so you can put lookups or +conditions there. When the right-hand side evaluates to "0" or +"false", no scanning will be done and the condition will fail +immediately. + +If you do not want to scan for a particular user, but rather +use the SpamAssassin system-wide default profile, you can scan +for an unknown user, or simply use "nobody". + +The "spam" condition will return true if the threshold +specified in the user's SpamAssassin profile has been matched +or exceeded. If you want to use the spam condition for its +side effects (see the variables below), you can make it always +return "true" by appending ":true" to the username. + +When the condition is run, it sets up the following expansion +variables: + + $spam_score The spam score of the message, for example + "3.4" or "30.5". This is useful for + inclusion in log or reject messages. + + $spam_score_int The spam score of the message, multiplied + by ten, as an integer value. For example + "34" or "305". This is useful for numeric + comparisons in conditions. See further + below for a more complicated example. This + variable is special, since it is written + to the spool file, so it can be used + during the whole life of the message on + your exim system, even in routers + or transports. + + $spam_bar A string consisting of a number of '+' or + '-' characters, representing the + spam_score value. A spam score of "4.4" + would have a spam_bar of '++++'. This is + useful for inclusion in warning headers, + since MUAs can match on such strings. + + $spam_report A multiline text table, containing the + full SpamAssassin report for the message. + Useful for inclusion in headers or reject + messages. + +The spam condition caches its results. If you call it again +with the same user name, it will not really scan again, but +rather return the same values as before. + +The spam condition will return DEFER if there is any error +while running the message through SpamAssassin. If you want to +treat DEFER as FAIL (to pass on to the next ACL statement +block), append '/defer_ok' to the right hand side of the spam +condition, like this: + +/* --------------- +deny message = This message was classified as SPAM + spam = joe/defer_ok +---------------- */ + +This will cause messages to be accepted even if there is a +problem with spamd. + +Finally, here is a commented example on how to use the spam +condition: + +/* ---------------- +# put headers in all messages (no matter if spam or not) +warn message = X-Spam-Score: $spam_score ($spam_bar) + spam = nobody:true +warn message = X-Spam-Report: $spam_report + spam = nobody:true + +# add second subject line with *SPAM* marker when message +# is over threshold +warn message = Subject: *SPAM* $h_Subject + spam = nobody + +# reject spam at high scores (> 12) +deny message = This message scored $spam_score spam points. + spam = nobody:true + condition = ${if >{$spam_score_int}{120}{1}{0}} +----------------- */ + + + +4. The "malware" facility + Scan messages for viruses using an external virus scanner +-------------------------------------------------------------- + +This facility lets you connect virus scanner software to exim. +It supports a "generic" interface to scanners called via the +shell, and specialized interfaces for "daemon" type virus +scanners, who are resident in memory and thus are much faster. + +To use this facility, you MUST set the "av_scanner" option in +section 1 of the exim config file. It specifies the scanner +type to use, and any additional options it needs to run. The +basic syntax is as follows: + + av_scanner = :::[...] + +The following scanner-types are supported in this release: + + sophie Sophie is a daemon that uses Sophos' libsavi + library to scan for viruses. You can get Sophie + at http://www.vanja.com/tools/sophie/. The only + option for this scanner type is the path to the + UNIX socket that Sophie uses for client + communication. The default path is + /var/run/sophie, so if you are using this, you + can omit the option. Example: + + av_scanner = sophie:/tmp/sophie + + aveserver This is the scanner daemon of Kaspersky Version + 5. You can get a trial version at + http://www.kaspersky.com. This scanner type + takes one option, which is the path to the + daemon's UNIX socket. The default is + "/var/run/aveserver". Example: + + av_scanner = aveserver:/var/run/aveserver + + + kavdaemon This is the scanner daemon of Kaspersky Version + 4. This version of the Kaspersky scanner is + outdated. Please upgrade (see "aveserver" + above). This scanner type takes one option, + which is the path to the daemon's UNIX socket. + The default is "/var/run/AvpCtl". Example: + + av_scanner = kavdaemon:/opt/AVP/AvpCtl + + + clamd Another daemon type scanner, this one is GPL and + free. Get it at http://clamav.elektrapro.com/. + Clamd does not seem to unpack MIME containers, + so it is recommended to use the demime facility + with it. It takes one option: either the path + and name of a UNIX socket file, or a + hostname/port pair, separated by space. If + unset, the default is "/tmp/clamd". Example: + + av_scanner = clamd:192.168.2.100 1234 + or + av_scanner = clamd:/opt/clamd/socket + + + drweb This one is for the DrWeb (http://www.sald.com/) + daemon. It takes one argument, either a full + path to a UNIX socket, or an IP address and port + separated by whitespace. If you omit the + argument, the default + + /usr/local/drweb/run/drwebd.sock + + is used. Example: + + av_scanner = drweb:192.168.2.20 31337 + or + av_scanner = drweb:/var/run/drwebd.sock + + Thanks to Alex Miller for + contributing the code for this scanner. + + + mksd Yet another daemon type scanner, aimed mainly at + Polish users, though some parts of documentation + are now avaliable in English. You can get it at + http://linux.mks.com.pl/. The only option for + this scanner type is the maximum number of + processes used simultaneously to scan the + attachments, provided that the demime facility + is employed and also mksd has been run with + at least the same number of child processes. + You can safely omit this option, the default + value is 1. Example: + + av_scanner = mksd:2 + + + cmdline This is the keyword for the generic command line + scanner interface. It can be used to attach + virus scanners that are invoked on the shell. + This scanner type takes 3 mantadory options: + + - full path and name of the scanner binary, with + all command line options and a placeholder + (%s) for the directory to scan. + + - A regular expression to match against the + STDOUT and STDERR output of the virus scanner. + If the expression matches, a virus was found. + You must make absolutely sure that this + expression only matches on "virus found". This + is called the "trigger" expression. + + - Another regular expression, containing exactly + ONE pair of braces, to match the name of the + virus found in the scanners output. This is + called the "name" expression. + + Example: + + Sophos Sweep reports a virus on a line like + this: + + Virus 'W32/Magistr-B' found in file ./those.bat + + For the "trigger" expression, we just use the + "found" word. For the "name" expression, we want + to get the W32/Magistr-B string, so we can match + for the single quotes left and right of it, + resulting in the regex '(.*)' (WITH the quotes!) + + Altogether, this makes the configuration + setting: + + av_scanner = cmdline:\ + /path/to/sweep -all -rec -archive %s:\ + found:'(.+)' + +When av_scanner is correcly set, you can use the "malware" +condition in the DATA ACL. The condition takes a right-hand +argument that is expanded before use. It can then be one of + + - "true", "*", or "1", in which case the message is scanned + for viruses. The condition will succeed if a virus was + found, or fail otherwise. This is the recommended usage. + + - "false" or "0", in which case no scanning is done and the + condition will fail immediately. + + - a regular expression, in which case the message is scanned + for viruses. The condition will succeed if a virus found + found and its name matches the regular expression. This + allows you to take special actions on certain types of + viruses. + +When a virus was found, the condition sets up an expansion +variable called $malware_name that contains the name of the +virus found. You should use it in a "message" modifier that +contains the error returned to the sender. + +The malware condition caches its results, so when you use it +multiple times, the actual scanning process is only carried +out once. + +If your virus scanner cannot unpack MIME and TNEF containers +itself, you should use the demime condition prior to the +malware condition. + +Here is a simple example: + +/* ---------------------- +deny message = This message contains malware ($malware_name) + demime = * + malware = * +---------------------- */ + +Like with the "spam" condition, you can append '/defer_ok' to +the malware condition to accept messages even if there is a +problem with the virus scanner, like this: + +/* ---------------------- +deny message = This message contains malware ($malware_name) + demime = * + malware = */defer_ok +---------------------- */ + +ADVANCED TIP: When the av_scanner option starts with a dollar +($) sign, it is expanded before being used. This is useful if +you want to use multiple scanners. You can then set + +/* ---------------------- +av_scanner = $acl_m0 +---------------------- */ + +and use these ACL blocks to scan with both sophie and +aveserver scanners: + +/* ---------------------- +deny message = This message contains malware ($malware_name) + set acl_m0 = sophie + malware = * + +deny message = This message contains malware ($malware_name) + set acl_m0 = aveserver + malware = * +---------------------- */ + +However, when av_scanner is expanded, the result caching of +the malware condition is not used, so each malware condition +call results in a new scan of the message. + + + +5. The "demime" facility + MIME unpacking, sanity checking and file extension blocking +-------------------------------------------------------------- + +* This facility provides a simpler interface to MIME decoding +* than the MIME ACL functionality. It is kept in exiscan for +* backward compatability. + +The demime facility unpacks MIME containers in the message. It +detects errors in MIME containers and can match file +extensions found in the message against a list. Using this +facility will produce additional files in the temporary scan +directory that contain the unpacked MIME parts of the message. +If you do antivirus scanning, it is recommened to use the +"demime" condition before the antivirus ("malware") condition. + +The condition name of this facility is "demime". On the right +hand side, you can pass a colon-separated list of file +extensions that it should match against. If one of the file +extensions is found, the condition will return "OK" (or +"true"), otherwise it will return FAIL (or "false"). If there +was any TEMPORARY error while demimeing (mostly "disk full"), +the condition will return DEFER, and the message will be +temporarily rejected. + +The right-hand side gets "expanded" before being treated as a +list, so you can have conditions and lookups there. If it +expands to an empty string, "false", or zero ("0"), no +demimeing is done and the conditions returns FALSE. + +A short example: + +/* ------------ +deny message = Found blacklisted file attachment + demime = vbs:com:bat:pif:prf:lnk +--------------- */ + +When the condition is run, it sets up the following expansion +variables: + + $demime_errorlevel When an error was detected in a MIME + container, this variable contains the + "severity" of the error, as an integer + number. The higher the value, the + more severe the error. If this + variable is unset or zero, no error has + occured. + + $demime_reason When $demime_errorlevel is greater than + zero, this variable contains a human + -readable text string describing the + MIME error that occured. + + $found_extension When the "demime" condition returns + "true", this variable contains the file + extension it has found. + +Both $demime_errorlevel and $demime_reason are set with the +first call of the "demime" condition, and are not changed on +subsequent calls. + +If do not want to check for any file extensions, but rather +use the demime facility for unpacking or error checking +purposes, just pass "*" as the right-hand side value. + +Here is a more elaborate example on how to use this facility: + +/* ----------------- +# Reject messages with serious MIME container errors +deny message = Found MIME error ($demime_reason). + demime = * + condition = ${if >{$demime_errorlevel}{2}{1}{0}} + +# Reject known virus spreading file extensions. +# Accepting these is pretty much braindead. +deny message = contains $found_extension file (blacklisted). + demime = com:vbs:bat:pif:scr + +# Freeze .exe and .doc files. Postmaster can +# examine them and eventually thaw them up. +deny log_message = Another $found_extension file. + demime = exe:doc + control = freeze +--------------------- */ + + + +6. The "fakereject" control statement + Reject a message while really accepting it. +-------------------------------------------------------------- + +When you put "control = fakereject" in an ACL statement, the +following will happen: If exim would have accepted the +message, it will tell the remote host that it did not, with a +message of: + +550-FAKE_REJECT id=xxxxxx-xxxxxx-xx +550-Your message has been rejected but is being kept for evaluation. +550 If it was a legit message, it may still be delivered to the target recipient(s). + +But exim will go on to treat the message as if it had accepted +it. This should be used with extreme caution, please look into +the examples document for possible usage. + + + +7. Brighmail AntiSpam (BMI) suppport +-------------------------------------------------------------- + +Brightmail AntiSpam is a commercial package. Please see +http://www.brightmail.com for more information on +the product. For the sake of clarity, we'll refer to it as +"BMI" from now on. + + +0) BMI concept and implementation overview + +In contrast to how spam-scanning with SpamAssassin is +implemented in exiscan-acl, BMI is more suited for per +-recipient scanning of messages. However, each messages is +scanned only once, but multiple "verdicts" for multiple +recipients can be returned from the BMI server. The exiscan +implementation passes the message to the BMI server just +before accepting it. It then adds the retrieved verdicts to +the messages header file in the spool. These verdicts can then +be queried in routers, where operation is per-recipient +instead of per-message. To use BMI, you need to take the +following steps: + + 1) Compile Exim with BMI support + 2) Set up main BMI options (top section of exim config file) + 3) Set up ACL control statement (ACL section of the config + file) + 4) Set up your routers to use BMI verdicts (routers section + of the config file). + 5) (Optional) Set up per-recipient opt-in information. + +These four steps are explained in more details below. + +1) Adding support for BMI at compile time + + To compile with BMI support, you need to link Exim against + the Brighmail client SDK, consisting of a library + (libbmiclient_single.so) and a header file (bmi_api.h). + You'll also need to explicitly set a flag in the Makefile to + include BMI support in the Exim binary. Both can be achieved + with these 2 lines in Local/Makefile: + + CFLAGS=-DBRIGHTMAIL -I/path/to/the/dir/with/the/includefile + EXTRALIBS_EXIM=-L/path/to/the/dir/with/the/library -lbmiclient_single + + If you use other CFLAGS or EXTRALIBS_EXIM settings then + merge the content of these lines with them. + + Note for BMI6.x users: You'll also have to add -lxml2_single + to the EXTRALIBS_EXIM line. Users of 5.5x do not need to do + this. + + You should also include the location of + libbmiclient_single.so in your dynamic linker configuration + file (usually /etc/ld.so.conf) and run "ldconfig" + afterwards, or else the produced Exim binary will not be + able to find the library file. + + +2) Setting up BMI support in the exim main configuration + + To enable BMI support in the main exim configuration, you + should set the path to the main BMI configuration file with + the "bmi_config_file" option, like this: + + bmi_config_file = /opt/brightmail/etc/brightmail.cfg + + This must go into section 1 of exims configuration file (You + can put it right on top). If you omit this option, it + defaults to /opt/brightmail/etc/brightmail.cfg. + + Note for BMI6.x users: This file is in XML format in V6.xx + and its name is /opt/brightmail/etc/bmiconfig.xml. So BMI + 6.x users MUST set the bmi_config_file option. + + +3) Set up ACL control statement + + To optimize performance, it makes sense only to process + messages coming from remote, untrusted sources with the BMI + server. To set up a messages for processing by the BMI + server, you MUST set the "bmi_run" control statement in any + ACL for an incoming message. You will typically do this in + an "accept" block in the "acl_check_rcpt" ACL. You should + use the "accept" block(s) that accept messages from remote + servers for your own domain(s). Here is an example that uses + the "accept" blocks from exims default configuration file: + + + accept domains = +local_domains + endpass + verify = recipient + control = bmi_run + + accept domains = +relay_to_domains + endpass + verify = recipient + control = bmi_run + + If bmi_run is not set in any ACL during reception of the + message, it will NOT be passed to the BMI server. + + +4) Setting up routers to use BMI verdicts + + When a message has been run through the BMI server, one or + more "verdicts" are present. Different recipients can have + different verdicts. Each recipient is treated individually + during routing, so you can query the verdicts by recipient + at that stage. From Exims view, a verdict can have the + following outcomes: + + o deliver the message normally + o deliver the message to an alternate location + o do not deliver the message + + To query the verdict for a recipient, the implementation + offers the following tools: + + + - Boolean router preconditions. These can be used in any + router. For a simple implementation of BMI, these may be + all that you need. The following preconditions are + available: + + o bmi_deliver_default + + This precondition is TRUE if the verdict for the + recipient is to deliver the message normally. If the + message has not been processed by the BMI server, this + variable defaults to TRUE. + + o bmi_deliver_alternate + + This precondition is TRUE if the verdict for the + recipient is to deliver the message to an alternate + location. You can get the location string from the + $bmi_alt_location expansion variable if you need it. See + further below. If the message has not been processed by + the BMI server, this variable defaults to FALSE. + + o bmi_dont_deliver + + This precondition is TRUE if the verdict for the + recipient is NOT to deliver the message to the + recipient. You will typically use this precondition in a + top-level blackhole router, like this: + + # don't deliver messages handled by the BMI server + bmi_blackhole: + driver = redirect + bmi_dont_deliver + data = :blackhole: + + This router should be on top of all others, so messages + that should not be delivered do not reach other routers + at all. If the message has not been processed by + the BMI server, this variable defaults to FALSE. + + + - A list router precondition to query if rules "fired" on + the message for the recipient. Its name is "bmi_rule". You + use it by passing it a colon-separated list of rule + numbers. You can use this condition to route messages that + matched specific rules. Here is an example: + + # special router for BMI rule #5, #8 and #11 + bmi_rule_redirect: + driver = redirect + bmi_rule = 5:8:11 + data = postmaster@mydomain.com + + + - Expansion variables. Several expansion variables are set + during routing. You can use them in custom router + conditions, for example. The following variables are + available: + + o $bmi_base64_verdict + + This variable will contain the BASE64 encoded verdict + for the recipient being routed. You can use it to add a + header to messages for tracking purposes, for example: + + localuser: + driver = accept + check_local_user + headers_add = X-Brightmail-Verdict: $bmi_base64_verdict + transport = local_delivery + + If there is no verdict available for the recipient being + routed, this variable contains the empty string. + + o $bmi_base64_tracker_verdict + + This variable will contain a BASE64 encoded subset of + the verdict information concerning the "rules" that + fired on the message. You can add this string to a + header, commonly named "X-Brightmail-Tracker". Example: + + localuser: + driver = accept + check_local_user + headers_add = X-Brightmail-Tracker: $bmi_base64_tracker_verdict + transport = local_delivery + + If there is no verdict available for the recipient being + routed, this variable contains the empty string. + + o $bmi_alt_location + + If the verdict is to redirect the message to an + alternate location, this variable will contain the + alternate location string returned by the BMI server. In + its default configuration, this is a header-like string + that can be added to the message with "headers_add". If + there is no verdict available for the recipient being + routed, or if the message is to be delivered normally, + this variable contains the empty string. + + o $bmi_deliver + + This is an additional integer variable that can be used + to query if the message should be delivered at all. You + should use router preconditions instead if possible. + + $bmi_deliver is '0': the message should NOT be delivered. + $bmi_deliver is '1': the message should be delivered. + + + IMPORTANT NOTE: Verdict inheritance. + The message is passed to the BMI server during message + reception, using the target addresses from the RCPT TO: + commands in the SMTP transaction. If recipients get expanded + or re-written (for example by aliasing), the new address(es) + inherit the verdict from the original address. This means + that verdicts also apply to all "child" addresses generated + from top-level addresses that were sent to the BMI server. + + +5) Using per-recipient opt-in information (Optional) + + The BMI server features multiple scanning "profiles" for + individual recipients. These are usually stored in a LDAP + server and are queried by the BMI server itself. However, + you can also pass opt-in data for each recipient from the + MTA to the BMI server. This is particularly useful if you + already look up recipient data in exim anyway (which can + also be stored in a SQL database or other source). This + implementation enables you to pass opt-in data to the BMI + server in the RCPT ACL. This works by setting the + 'bmi_optin' modifier in a block of that ACL. If should be + set to a list of comma-separated strings that identify the + features which the BMI server should use for that particular + recipient. Ideally, you would use the 'bmi_optin' modifier + in the same ACL block where you set the 'bmi_run' control + flag. Here is an example that will pull opt-in data for each + recipient from a flat file called + '/etc/exim/bmi_optin_data'. + + The file format: + + user1@mydomain.com: : + user2@thatdomain.com: + + + The example: + + accept domains = +relay_to_domains + endpass + verify = recipient + bmi_optin = ${lookup{$local_part@$domain}lsearch{/etc/exim/bmi_optin_data}} + control = bmi_run + + Of course, you can also use any other lookup method that + exim supports, including LDAP, Postgres, MySQL, Oracle etc., + as long as the result is a list of colon-separated opt-in + strings. + + For a list of available opt-in strings, please contact your + Brightmail representative. + + + + +8. Sender Policy Framework (SPF) support +-------------------------------------------------------------- + +To learn more about SPF, visit http://spf.pobox.com. This +document does not explain the SPF fundamentals, you should +read and understand the implications of deploying SPF on your +system before doing so. + +SPF support is added via the libspf2 library. Visit + + http://www.libspf2.org/ + +to obtain a copy, then compile and install it. By default, +this will put headers in /usr/local/include and the static +library in /usr/local/lib. + +To compile exim with SPF support, set these additional flags in +Local/Makefile: + +CFLAGS=-DSPF -I/usr/local/include +EXTRALIBS_EXIM=-L/usr/local/lib -lspf2 + +This assumes that the libspf2 files are installed in +their default locations. + +You can now run SPF checks in incoming SMTP by using the "spf" +ACL condition in either the MAIL, RCPT or DATA ACLs. When +using it in the RCPT ACL, you can make the checks dependend on +the RCPT address (or domain), so you can check SPF records +only for certain target domains. This gives you the +possibility to opt-out certain customers that do not want +their mail to be subject to SPF checking. + +The spf condition takes a list of strings on its right-hand +side. These strings describe the outcome of the SPF check for +which the spf condition should succeed. Valid strings are: + + o pass The SPF check passed, the sending host + is positively verified by SPF. + o fail The SPF check failed, the sending host + is NOT allowed to send mail for the domain + in the envelope-from address. + o softfail The SPF check failed, but the queried + domain can't absolutely confirm that this + is a forgery. + o none The queried domain does not publish SPF + records. + o neutral The SPF check returned a "neutral" state. + This means the queried domain has published + a SPF record, but wants to allow outside + servers to send mail under its domain as well. + o err_perm This indicates a syntax error in the SPF + record of the queried domain. This should be + treated like "none". + o err_temp This indicates a temporary error during all + processing, including exim's SPF processing. + You may defer messages when this occurs. + +You can prefix each string with an exclamation mark to invert +is meaning, for example "!fail" will match all results but +"fail". The string list is evaluated left-to-right, in a +short-circuit fashion. When a string matches the outcome of +the SPF check, the condition succeeds. If none of the listed +strings matches the outcome of the SPF check, the condition +fails. + +Here is a simple example to fail forgery attempts from domains +that publish SPF records: + +/* ----------------- +deny message = $sender_host_address is not allowed to send mail from $sender_address_domain + spf = fail +--------------------- */ + +You can also give special treatment to specific domains: + +/* ----------------- +deny message = AOL sender, but not from AOL-approved relay. + sender_domains = aol.com + spf = fail:neutral +--------------------- */ + +Explanation: AOL publishes SPF records, but is liberal and +still allows non-approved relays to send mail from aol.com. +This will result in a "neutral" state, while mail from genuine +AOL servers will result in "pass". The example above takes +this into account and treats "neutral" like "fail", but only +for aol.com. Please note that this violates the SPF draft. + +When the spf condition has run, it sets up several expansion +variables. + + $spf_header_comment + This contains a human-readable string describing the outcome + of the SPF check. You can add it to a custom header or use + it for logging purposes. + + $spf_received + This contains a complete SPF-Received: header that can be + added to the message. Please note that according to the SPF + draft, this header must be added at the top of the header + list. Please see section 10 on how you can do this. + + $spf_result + This contains the outcome of the SPF check in string form, + one of pass, fail, softfail, none, neutral, err_perm or + err_temp. + + $spf_smtp_comment + This contains a string that can be used in a SMTP response + to the calling party. Useful for "fail". + + + +9. SRS (Sender Rewriting Scheme) Support +-------------------------------------------------------------- + +Exiscan currently includes SRS support via Miles Wilton's +libsrs_alt library. His patch to link to that library is +included in exiscan. The current version of the supported +library is 0.4. + +In order to use SRS, you must get a copy of libsrs_alt from + +http://srs.mirtol.com/ + +Unpack the tarball, then refer to MTAs/README.EXIM +to proceed. + +The SRS support is subject to change and should be considered +experimental. SRS itself is still subject to changes. + + + +10. Selecting header positions when adding headers in ACLs +-------------------------------------------------------------- + +When adding headers to a message during ACL processing (using +the "message" keyword), the exiscan-acl patch enables you to +select where those headers are added in the list. This is done +via a "control" statement. Possible statements are: + + control = header_pos_top + This will add headers at the top of the header list, even + before the Received: headers. This is useful for adding + SPF-Received: headers. + + control = header_pos_middle + This will add headers behind the last Received: or Resent-*: + header found in the list. This is good if you want to be + RFC822/2822 compliant. + + control = header_pos_bottom + This is the default. It will add headers at the bottom of + the list, just like vanilla exim would do. + +When an ACL is run, the setting is reset to the default. +Inside one ACL, the setting is remembered between verb blocks, +but you can change it as often as you like. + +This feature was added mainly to provide proper SPF draft +support. It should be considered experimental and might change +to be more flexible in the future. + +-------------------------------------------------------------- +End of file +-------------------------------------------------------------- diff -urN exim-4.41-orig/exim_monitor/em_globals.c exim-4.41/exim_monitor/em_globals.c --- exim-4.41-orig/exim_monitor/em_globals.c Thu Jul 22 10:46:53 2004 +++ exim-4.41/exim_monitor/em_globals.c Thu Jul 22 16:31:13 2004 @@ -42,6 +42,10 @@ uschar *action_required; uschar *alternate_config = NULL; +#ifdef BRIGHTMAIL +int bmi_run = 0; +uschar *bmi_verdicts = NULL; +#endif int body_max = 20000; uschar *exim_path = US BIN_DIRECTORY "/exim" @@ -130,6 +134,8 @@ BOOL deliver_manual_thaw = FALSE; BOOL dont_deliver = FALSE; +BOOL fake_reject = FALSE; + header_line *header_last = NULL; header_line *header_list = NULL; @@ -139,6 +145,7 @@ BOOL local_error_message = FALSE; uschar *local_scan_data = NULL; +uschar *spam_score_int = NULL; BOOL log_timezone = FALSE; int message_age = 0; uschar *message_id; diff -urN exim-4.41-orig/scripts/MakeLinks exim-4.41/scripts/MakeLinks --- exim-4.41-orig/scripts/MakeLinks Thu Jul 22 10:46:52 2004 +++ exim-4.41/scripts/MakeLinks Thu Jul 22 16:31:13 2004 @@ -170,19 +170,27 @@ # but local_scan.c does not, because its location is taken from the build-time # configuration. Likewise for the os.c file, which gets build dynamically. +ln -s ../src/bmi_spam.h bmi_spam.h ln -s ../src/dbfunctions.h dbfunctions.h ln -s ../src/dbstuff.h dbstuff.h +ln -s ../src/demime.h demime.h ln -s ../src/exim.h exim.h ln -s ../src/functions.h functions.h ln -s ../src/globals.h globals.h ln -s ../src/local_scan.h local_scan.h ln -s ../src/macros.h macros.h +ln -s ../src/mime.h mime.h ln -s ../src/mytypes.h mytypes.h ln -s ../src/osfunctions.h osfunctions.h +ln -s ../src/spam.h spam.h +ln -s ../src/spf.h spf.h +ln -s ../src/srs.h srs.h ln -s ../src/store.h store.h ln -s ../src/structs.h structs.h +ln -s ../src/tnef.h tnef.h ln -s ../src/acl.c acl.c +ln -s ../src/bmi_spam.c bmi_spam.c ln -s ../src/buildconfig.c buildconfig.c ln -s ../src/child.c child.c ln -s ../src/crypt16.c crypt16.c @@ -190,6 +198,7 @@ ln -s ../src/dbfn.c dbfn.c ln -s ../src/debug.c debug.c ln -s ../src/deliver.c deliver.c +ln -s ../src/demime.c demime.c ln -s ../src/directory.c directory.c ln -s ../src/dns.c dns.c ln -s ../src/drtables.c drtables.c @@ -208,7 +217,9 @@ ln -s ../src/ip.c ip.c ln -s ../src/log.c log.c ln -s ../src/lss.c lss.c +ln -s ../src/malware.c malware.c ln -s ../src/match.c match.c +ln -s ../src/mime.c mime.c ln -s ../src/moan.c moan.c ln -s ../src/parse.c parse.c ln -s ../src/perl.c perl.c @@ -216,6 +227,7 @@ ln -s ../src/rda.c rda.c ln -s ../src/readconf.c readconf.c ln -s ../src/receive.c receive.c +ln -s ../src/regex.c regex.c ln -s ../src/retry.c retry.c ln -s ../src/rewrite.c rewrite.c ln -s ../src/rfc2047.c rfc2047.c @@ -224,13 +236,18 @@ ln -s ../src/sieve.c sieve.c ln -s ../src/smtp_in.c smtp_in.c ln -s ../src/smtp_out.c smtp_out.c +ln -s ../src/spam.c spam.c +ln -s ../src/spf.c spf.c ln -s ../src/spool_in.c spool_in.c +ln -s ../src/spool_mbox.c spool_mbox.c ln -s ../src/spool_out.c spool_out.c +ln -s ../src/srs.c srs.c ln -s ../src/store.c store.c ln -s ../src/string.c string.c ln -s ../src/tls.c tls.c ln -s ../src/tls-gnu.c tls-gnu.c ln -s ../src/tls-openssl.c tls-openssl.c +ln -s ../src/tnef.c tnef.c ln -s ../src/tod.c tod.c ln -s ../src/transport.c transport.c ln -s ../src/tree.c tree.c diff -urN exim-4.41-orig/src/acl.c exim-4.41/src/acl.c --- exim-4.41-orig/src/acl.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/acl.c Mon Aug 2 13:39:47 2004 @@ -7,8 +7,13 @@ /* Code for handling Access Control Lists (ACLs) */ +/* This file has been modified by the exiscan-acl patch. */ + #include "exim.h" +#ifdef BRIGHTMAIL +#include "bmi_spam.h" +#endif /* Default callout timeout */ @@ -32,19 +37,33 @@ /* ACL condition and modifier codes - keep in step with the table that follows. */ -enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY, +enum { ACLC_ACL, ACLC_AUTHENTICATED, +#ifdef BRIGHTMAIL + ACLC_BMI_OPTIN, +#endif + ACLC_CONDITION, ACLC_CONTROL, ACLC_DECODE, ACLC_DELAY, ACLC_DEMIME, ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS, - ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MESSAGE, - ACLC_RECIPIENTS, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY }; + ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MALWARE, ACLC_MESSAGE, ACLC_MIME_REGEX, + ACLC_RECIPIENTS, ACLC_REGEX, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_SPAM, +#ifdef SPF + ACLC_SPF, +#endif + ACLC_VERIFY }; /* ACL conditions/modifiers: "delay", "control", "endpass", "message", "log_message", "logwrite", and "set" are modifiers that look like conditions but always return TRUE. They are used for their side effects. */ -static uschar *conditions[] = { US"acl", US"authenticated", US"condition", - US"control", US"delay", US"dnslists", US"domains", US"encrypted", - US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite", - US"message", US"recipients", US"sender_domains", US"senders", US"set", +static uschar *conditions[] = { US"acl", US"authenticated", +#ifdef BRIGHTMAIL + US"bmi_optin", +#endif + US"condition", US"control", US"decode", US"delay", US"demime", US"dnslists", US"domains", US"encrypted", + US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite", US"malware", + US"message", US"mime_regex", US"recipients", US"regex", US"sender_domains", US"senders", US"set", US"spam", +#ifdef SPF + US"spf", +#endif US"verify" }; /* Flags to indicate for which conditions /modifiers a string expansion is done @@ -54,9 +73,14 @@ static uschar cond_expand_at_top[] = { TRUE, /* acl */ FALSE, /* authenticated */ +#ifdef BRIGHTMAIL + TRUE, /* bmi_optin */ +#endif TRUE, /* condition */ TRUE, /* control */ + TRUE, /* decode */ TRUE, /* delay */ + TRUE, /* demime */ TRUE, /* dnslists */ FALSE, /* domains */ FALSE, /* encrypted */ @@ -65,11 +89,18 @@ FALSE, /* local_parts */ TRUE, /* log_message */ TRUE, /* logwrite */ + TRUE, /* malware */ TRUE, /* message */ + TRUE, /* mime_regex */ FALSE, /* recipients */ + TRUE, /* regex */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ + TRUE, /* spam */ +#ifdef SPF + TRUE, /* spf */ +#endif TRUE /* verify */ }; @@ -78,9 +109,14 @@ static uschar cond_modifiers[] = { FALSE, /* acl */ FALSE, /* authenticated */ +#ifdef BRIGHTMAIL + TRUE, /* bmi_optin */ +#endif FALSE, /* condition */ TRUE, /* control */ + FALSE, /* decode */ TRUE, /* delay */ + FALSE, /* demime */ FALSE, /* dnslists */ FALSE, /* domains */ FALSE, /* encrypted */ @@ -89,11 +125,18 @@ FALSE, /* local_parts */ TRUE, /* log_message */ TRUE, /* log_write */ + FALSE, /* malware */ TRUE, /* message */ + FALSE, /* mime_regex */ FALSE, /* recipients */ + FALSE, /* regex */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ + FALSE, /* spam */ +#ifdef SPF + FALSE, /* spf */ +#endif FALSE /* verify */ }; @@ -102,8 +145,20 @@ static unsigned int cond_forbids[] = { 0, /* acl */ + (1<next = NULL; h->type = htype_other; h->slen = hlen; + h->want_pos = acl_header_pos; *hptr = h; hptr = &(h->next); @@ -1043,6 +1159,7 @@ uschar *user_message = NULL; uschar *log_message = NULL; int rc = OK; +int sep = '/'; for (; cb != NULL; cb = cb->next) { @@ -1162,7 +1279,7 @@ break; case ACLC_CONTROL: - if (where != ACL_WHERE_MAIL && where != ACL_WHERE_RCPT && + if (where != ACL_WHERE_MAIL && where != ACL_WHERE_MIME && where != ACL_WHERE_RCPT && where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP) { *log_msgptr = string_sprintf("cannot use \"control\" in %s ACL", @@ -1174,6 +1291,32 @@ deliver_freeze = TRUE; deliver_frozen_at = time(NULL); } + else if (Ustrcmp(arg, "header_pos_top") == 0) + { + acl_header_pos = HEADER_POS_TOP; + } + else if (Ustrcmp(arg, "header_pos_middle") == 0) + { + acl_header_pos = HEADER_POS_MIDDLE; + } + else if (Ustrcmp(arg, "header_pos_bottom") == 0) + { + acl_header_pos = HEADER_POS_BOTTOM; + } + else if (Ustrcmp(arg, "fakereject") == 0) + { + fake_reject = TRUE; + } +#ifdef BRIGHTMAIL + else if (Ustrcmp(arg, "bmi_run") == 0) + { + bmi_run = 1; + } +#endif + else if (Ustrcmp(arg, "no_mbox_unspool") == 0) + { + no_mbox_unspool = TRUE; + } else if (Ustrcmp(arg, "queue_only") == 0) { queue_only_policy = TRUE; @@ -1227,7 +1370,76 @@ break; case ACLC_DNSLISTS: - rc = verify_check_dnsbl(&arg); + rc = verify_check_dnsbl(&arg); + break; + +#ifdef BRIGHTMAIL + case ACLC_BMI_OPTIN: + { + int old_pool = store_pool; + store_pool = POOL_PERM; + bmi_current_optin = string_copy(arg); + store_pool = old_pool; + } + break; +#endif + + case ACLC_DECODE: + rc = mime_decode(&arg); + break; + + case ACLC_MIME_REGEX: + rc = mime_regex(&arg); + break; + + case ACLC_DEMIME: + rc = demime(&arg); + break; + +#ifdef SPF + case ACLC_SPF: + rc = spf_process(&arg, sender_address); + break; +#endif + + case ACLC_MALWARE: + { + /* Seperate the regular expression and any optional parameters. */ + uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size); + /* Run the malware backend. */ + rc = malware(&ss); + /* Modify return code based upon the existance of options. */ + while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) + != NULL) { + if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) + { + /* FAIL so that the message is passed to the next ACL */ + rc = FAIL; + } + } + } + break; + + case ACLC_SPAM: + { + /* Seperate the regular expression and any optional parameters. */ + uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size); + /* Run the spam backend. */ + rc = spam(&ss); + /* Modify return code based upon the existance of options. */ + while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) + != NULL) { + if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) + { + /* FAIL so that the message is passed to the next ACL */ + rc = FAIL; + } + } + } + break; + + case ACLC_REGEX: + rc = regex(&arg); break; case ACLC_DOMAINS: @@ -1868,6 +2080,9 @@ *user_msgptr = *log_msgptr = NULL; sender_verified_failed = NULL; +/* reset acl_header_pos to default */ +acl_header_pos = HEADER_POS_BOTTOM; + if (where == ACL_WHERE_RCPT) { adb = address_defaults; @@ -1899,6 +2114,7 @@ if (where != ACL_WHERE_MAIL && where != ACL_WHERE_RCPT && where != ACL_WHERE_DATA && + where != ACL_WHERE_MIME && where != ACL_WHERE_NOTSMTP) { log_write(0, LOG_MAIN|LOG_PANIC, "\"discard\" verb not allowed in %s " diff -urN exim-4.41-orig/src/auths/pwcheck.c exim-4.41/src/auths/pwcheck.c --- exim-4.41-orig/src/auths/pwcheck.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/auths/pwcheck.c Thu Jul 22 16:31:13 2004 @@ -1,7 +1,7 @@ /* SASL server API implementation * Rob Siemborski * Tim Martin - * $Id: checkpw.c,v 1.49 2002/03/07 19:14:04 ken3 Exp $ + * $Id: pwcheck.c,v 1.1.1.1 2004/06/24 07:46:30 tkistner Exp $ */ /* * Copyright (c) 2001 Carnegie Mellon University. All rights reserved. diff -urN exim-4.41-orig/src/bmi_spam.c exim-4.41/src/bmi_spam.c --- exim-4.41-orig/src/bmi_spam.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/bmi_spam.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,474 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner + patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling Brightmail AntiSpam. */ + +#include "exim.h" +#include "bmi_spam.h" + +#ifdef BRIGHTMAIL + +uschar *bmi_current_optin = NULL; + +uschar *bmi_process_message(header_line *header_list, int data_fd) { + BmiSystem *system = NULL; + BmiMessage *message = NULL; + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + const BmiVerdict *verdict = NULL; + FILE *data_file; + uschar data_buffer[4096]; + uschar localhost[] = "127.0.0.1"; + uschar *host_address; + uschar *verdicts = NULL; + int i,j; + + err = bmiInitSystem(BMI_VERSION, (char *)bmi_config_file, &system); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type); + return NULL; + } + + err = bmiInitMessage(system, &message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type); + bmiFreeSystem(system); + return NULL; + } + + /* Send IP address of sending host */ + if (sender_host_address == NULL) + host_address = localhost; + else + host_address = sender_host_address; + err = bmiProcessConnection((char *)host_address, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, (char *)host_address); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send envelope sender address */ + err = bmiProcessFROM((char *)sender_address, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, (char *)sender_address); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send envelope recipients */ + for(i=0;ibmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) { + debug_printf("passing bmiOptin string: %s\n", r->bmi_optin); + bmiOptinInit(&optin); + err = bmiOptinMset(optin, r->bmi_optin, ':'); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + log_write(0, LOG_PANIC|LOG_MAIN, + "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, (char *)r->address, (char *)r->bmi_optin); + if (optin != NULL) + bmiOptinFree(optin); + optin = NULL; + }; + }; + + err = bmiAccumulateTO((char *)r->address, optin, message); + + if (optin != NULL) + bmiOptinFree(optin); + + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, (char *)r->address); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + }; + err = bmiEndTO(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send message headers */ + while (header_list != NULL) { + /* skip deleted headers */ + if (header_list->type == '*') { + header_list = header_list->next; + continue; + }; + err = bmiAccumulateHeaders((const char *)header_list->text, header_list->slen, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + header_list = header_list->next; + }; + err = bmiEndHeaders(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send body */ + data_file = fdopen(data_fd,"r"); + do { + j = fread(data_buffer, 1, sizeof(data_buffer), data_file); + if (j > 0) { + err = bmiAccumulateBody((const char *)data_buffer, j, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + }; + } while (j > 0); + err = bmiEndBody(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + + /* End message */ + err = bmiEndMessage(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* get store for the verdict string */ + verdicts = store_get(1); + *verdicts = '\0'; + + for ( err = bmiAccessFirstVerdict(message, &verdict); + verdict != NULL; + err = bmiAccessNextVerdict(message, verdict, &verdict) ) { + char *verdict_str; + + err = bmiCreateStrFromVerdict(verdict,&verdict_str); + if (!store_extend(verdicts, Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) { + /* can't allocate more store */ + return NULL; + }; + if (*verdicts != '\0') + Ustrcat(verdicts, US ":"); + Ustrcat(verdicts, US verdict_str); + bmiFreeStr(verdict_str); + }; + + DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts); + + if (Ustrlen(verdicts) == 0) + return NULL; + else + return verdicts; +} + + +int bmi_get_delivery_status(uschar *base64_verdict) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + int rc = 1; /* deliver by default */ + + /* always deliver when there is no verdict */ + if (base64_verdict == NULL) + return 1; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return 1; + }; + + err = bmiVerdictError(verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + /* deliver normally due to error */ + rc = 1; + } + else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { + /* deliver normally */ + rc = 1; + } + else if (bmiVerdictAccessDestination(verdict) == NULL) { + /* do not deliver */ + rc = 0; + } + else { + /* deliver to alternate location */ + rc = 1; + }; + + bmiFreeVerdict(verdict); + return rc; +} + + +uschar *bmi_get_alt_location(uschar *base64_verdict) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + uschar *rc = NULL; + + /* always deliver when there is no verdict */ + if (base64_verdict == NULL) + return NULL; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return NULL; + }; + + err = bmiVerdictError(verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + /* deliver normally due to error */ + rc = NULL; + } + else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { + /* deliver normally */ + rc = NULL; + } + else if (bmiVerdictAccessDestination(verdict) == NULL) { + /* do not deliver */ + rc = NULL; + } + else { + /* deliver to alternate location */ + rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1); + Ustrcpy(rc, bmiVerdictAccessDestination(verdict)); + rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0'; + }; + + bmiFreeVerdict(verdict); + return rc; +} + +uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + const BmiRecipient *recipient = NULL; + const char *verdict_str = NULL; + uschar *verdict_ptr; + uschar *verdict_buffer = NULL; + int sep = 0; + + /* return nothing if there are no verdicts available */ + if (bmi_verdicts == NULL) + return NULL; + + /* allocate room for the b64 verdict string */ + verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1); + + /* loop through verdicts */ + verdict_ptr = bmi_verdicts; + while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep, + verdict_buffer, + Ustrlen(bmi_verdicts)+1)) != NULL) { + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(verdict_str, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str); + return NULL; + }; + + /* loop through rcpts for this verdict */ + for ( recipient = bmiVerdictAccessFirstRecipient(verdict); + recipient != NULL; + recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) { + uschar *rcpt_local_part; + uschar *rcpt_domain; + + /* compare address against our subject */ + rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient); + rcpt_domain = Ustrchr(rcpt_local_part,'@'); + if (rcpt_domain == NULL) { + rcpt_domain = US""; + } + else { + *rcpt_domain = '\0'; + rcpt_domain++; + }; + + if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) && + (strcmpic(rcpt_domain, bmi_domain) == 0) ) { + /* found verdict */ + bmiFreeVerdict(verdict); + return (uschar *)verdict_str; + }; + }; + + bmiFreeVerdict(verdict); + }; + + return NULL; +} + + +uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + uschar *rc = NULL; + + /* always deliver when there is no verdict */ + if (base64_verdict == NULL) + return NULL; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return NULL; + }; + + /* create old tracker string from verdict */ + err = bmiCreateOldStrFromVerdict(verdict, &rc); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return NULL; + }; + + bmiFreeVerdict(verdict); + return rc; +} + + +int bmi_check_rule(uschar *base64_verdict, uschar *option_list) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + int rc = 0; + uschar *rule_num; + uschar *rule_ptr; + uschar rule_buffer[32]; + int sep = 0; + + + /* no verdict -> no rule fired */ + if (base64_verdict == NULL) + return 0; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return 0; + }; + + err = bmiVerdictError(verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + /* error -> no rule fired */ + bmiFreeVerdict(verdict); + return 0; + } + + /* loop through numbers */ + rule_ptr = option_list; + while ((rule_num = string_nextinlist(&rule_ptr, &sep, + rule_buffer, 32)) != NULL) { + int rule_int = -1; + + /* try to translate to int */ + sscanf(rule_num, "%d", &rule_int); + if (rule_int > 0) { + debug_printf("checking rule #%d\n", rule_int); + /* check if rule fired on the message */ + if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) { + debug_printf("rule #%d fired\n", rule_int); + rc = 1; + break; + }; + }; + }; + + bmiFreeVerdict(verdict); + return rc; +}; + +#endif diff -urN exim-4.41-orig/src/bmi_spam.h exim-4.41/src/bmi_spam.h --- exim-4.41-orig/src/bmi_spam.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/bmi_spam.h Thu Jul 22 16:31:13 2004 @@ -0,0 +1,26 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner + patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling Brightmail AntiSpam. */ + +#ifdef BRIGHTMAIL + +#include + +extern uschar *bmi_process_message(header_line *, int); +extern uschar *bmi_get_base64_verdict(uschar *, uschar *); +extern uschar *bmi_get_base64_tracker_verdict(uschar *); +extern int bmi_get_delivery_status(uschar *); +extern uschar *bmi_get_alt_location(uschar *); +extern int bmi_check_rule(uschar *,uschar *); + +extern uschar *bmi_current_optin; + +#endif diff -urN exim-4.41-orig/src/config.h.defaults exim-4.41/src/config.h.defaults --- exim-4.41-orig/src/config.h.defaults Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/config.h.defaults Thu Jul 22 16:31:13 2004 @@ -49,7 +49,7 @@ #define HAVE_CRYPT16 #define HAVE_SA_LEN #define HEADERS_CHARSET "ISO-8859-1" -#define HEADER_ADD_BUFFER_SIZE 8192 +#define HEADER_ADD_BUFFER_SIZE (8192 * 4) #define HEADER_MAXSIZE (1024*1024) #define INPUT_DIRECTORY_MODE 0750 @@ -102,7 +102,7 @@ #define SPOOL_DIRECTORY #define SPOOL_DIRECTORY_MODE 0750 #define SPOOL_MODE 0640 -#define STRING_SPRINTF_BUFFER_SIZE 8192 +#define STRING_SPRINTF_BUFFER_SIZE (8192 * 4) #define SUPPORT_A6 #define SUPPORT_CRYPTEQ diff -urN exim-4.41-orig/src/configure.default exim-4.41/src/configure.default --- exim-4.41-orig/src/configure.default Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/configure.default Thu Jul 22 16:31:13 2004 @@ -108,6 +108,26 @@ # You should not change that setting until you understand how ACLs work. +# The following ACL entries are used if you want to do content scanning with +# the exiscan-acl patch. When you uncomment one of these lines, you must also +# review the respective entries in the ACL section further below. + +# acl_smtp_mime = acl_check_mime +# acl_smtp_data = acl_check_content + +# This configuration variable defines the virus scanner that is used with +# the 'malware' ACL condition of the exiscan acl-patch. If you do not use +# virus scanning, leave it commented. Please read doc/exiscan-acl-readme.txt +# for a list of supported scanners. + +# av_scanner = sophie:/var/run/sophie + +# The following setting is only needed if you use the 'spam' ACL condition +# of the exiscan-acl patch. It specifies on which host and port the SpamAssassin +# "spamd" daemon is listening. If you do not use this condition, or you use +# the default of "127.0.0.1 783", you can omit this option. + +# spamd_address = 127.0.0.1 783 # Specify the domain you want to be added to all unqualified addresses # here. An unqualified address is one that does not contain an "@" character @@ -342,6 +362,56 @@ deny message = relay not permitted +# These access control lists are used for content scanning with the exiscan-acl +# patch. You must also uncomment the entries for acl_smtp_data and acl_smtp_mime +# (scroll up), otherwise the ACLs will not be used. IMPORTANT: the default entries here +# should be treated as EXAMPLES. You MUST read the file doc/exiscan-acl-spec.txt +# to fully understand what you are doing ... + +acl_check_mime: + + # Decode MIME parts to disk. This will support virus scanners later. + warn decode = default + + # File extension filtering. + deny message = Blacklisted file extension detected + condition = ${if match \ + {${lc:$mime_filename}} \ + {\N(\.exe|\.pif|\.bat|\.scr|\.lnk|\.com)$\N} \ + {1}{0}} + + # Reject messages that carry chinese character sets. + # WARNING: This is an EXAMPLE. + deny message = Sorry, noone speaks chinese here + condition = ${if eq{$mime_charset}{gb2312}{1}{0}} + + accept + +acl_check_content: + + # Reject virus infested messages. + deny message = This message contains malware ($malware_name) + malware = * + + # Always add X-Spam-Score and X-Spam-Report headers, using SA system-wide settings + # (user "nobody"), no matter if over threshold or not. + warn message = X-Spam-Score: $spam_score ($spam_bar) + spam = nobody:true + warn message = X-Spam-Report: $spam_report + spam = nobody:true + + # Add X-Spam-Flag if spam is over system-wide threshold + warn message = X-Spam-Flag: YES + spam = nobody + + # Reject spam messages with score over 10, using an extra condition. + deny message = This message scored $spam_score points. Congratulations! + spam = nobody:true + condition = ${if >{$spam_score_int}{100}{1}{0}} + + # finally accept all the rest + accept + ###################################################################### # ROUTERS CONFIGURATION # diff -urN exim-4.41-orig/src/deliver.c exim-4.41/src/deliver.c --- exim-4.41-orig/src/deliver.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/deliver.c Thu Jul 22 16:31:13 2004 @@ -10,6 +10,9 @@ #include "exim.h" +#ifdef BRIGHTMAIL +#include "bmi_spam.h" +#endif /* Data block for keeping track of subprocesses for parallel remote delivery. */ @@ -152,6 +155,13 @@ deliver_domain = addr->domain; self_hostname = addr->self_hostname; +#ifdef BRIGHTMAIL +bmi_deliver = 1; /* deliver by default */ +bmi_alt_location = NULL; +bmi_base64_verdict = NULL; +bmi_base64_tracker_verdict = NULL; +#endif + /* If there's only one address we can set everything. */ if (addr->next == NULL) @@ -201,6 +211,19 @@ deliver_localpart_suffix = addr->parent->suffix; } } + +#ifdef BRIGHTMAIL + /* Set expansion variables related to Brightmail AntiSpam */ + bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig); + bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict); + /* get message delivery status (0 - don't deliver | 1 - deliver) */ + bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict); + /* if message is to be delivered, get eventual alternate location */ + if (bmi_deliver == 1) { + bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict); + }; +#endif + } /* For multiple addresses, don't set local part, and leave the domain and diff -urN exim-4.41-orig/src/demime.c exim-4.41/src/demime.c --- exim-4.41-orig/src/demime.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/demime.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,1276 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for unpacking MIME containers. Called from acl.c. */ + +#include "exim.h" +#include "demime.h" + +uschar demime_reason_buffer[1024]; +struct file_extension *file_extensions = NULL; + +int demime(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *option; + uschar option_buffer[64]; + unsigned long long mbox_size; + FILE *mbox_file; + uschar defer_error_buffer[1024]; + int demime_rc = 0; + + /* reset found_extension variable */ + found_extension = NULL; + + /* try to find 1st option */ + if ((option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer))) != NULL) { + + /* parse 1st option */ + if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { + /* explicitly no demimeing */ + return FAIL; + }; + } + else { + /* no options -> no demimeing */ + return FAIL; + }; + + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "demime acl condition: error while creating mbox spool file"); + return DEFER; + }; + + /* call demimer if not already done earlier */ + if (!demime_ok) + demime_rc = mime_demux(mbox_file, defer_error_buffer); + + fclose(mbox_file); + + if (demime_rc == DEFER) { + /* temporary failure (DEFER => DEFER) */ + log_write(0, LOG_MAIN, + "demime acl condition: %s", defer_error_buffer); + return DEFER; + }; + + /* set demime_ok to avoid unpacking again */ + demime_ok = 1; + + /* check for file extensions, if there */ + while (option != NULL) { + struct file_extension *this_extension = file_extensions; + + /* Look for the wildcard. If it is found, we always return true. + The user must then use a custom condition to evaluate demime_errorlevel */ + if (Ustrcmp(option,"*") == 0) { + found_extension = NULL; + return OK; + }; + + /* loop thru extension list */ + while (this_extension != NULL) { + if (strcmpic(option, this_extension->file_extension_string) == 0) { + /* found one */ + found_extension = this_extension->file_extension_string; + return OK; + }; + this_extension = this_extension->next; + }; + + /* grab next extension from option list */ + option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer)); + }; + + /* nothing found */ + return FAIL; +} + + +/************************************************* +* unpack TNEF in given directory * +*************************************************/ + +int mime_unpack_tnef(uschar *directory) { + uschar filepath[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + /* open the dir */ + tempdir = opendir(CS directory); + if (tempdir == NULL) { + return -2; + }; + + /* loop thru dir */ + n = 0; + do { + entry = readdir(tempdir); + /* break on end of list */ + if (entry == NULL) break; + snprintf(CS filepath,1024,"%s/%s",directory,entry->d_name); + if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) && (Ustrcmp(entry->d_name,"winmail.dat") == 0) ) { + TNEF_set_path(CS directory); + n = TNEF_main(CS filepath); + }; + } while (1); + + closedir(tempdir); + return 0; +} + + +/************************************************* +* small hex_str -> integer conversion function * +*************************************************/ + +/* needed for quoted-printable +*/ + +unsigned int mime_hstr_i(uschar *cptr) { + unsigned int i, j = 0; + + while (cptr && *cptr && isxdigit(*cptr)) { + i = *cptr++ - '0'; + if (9 < i) i -= 7; + j <<= 4; + j |= (i & 0x0f); + } + + return(j); +} + + +/************************************************* +* decode quoted-printable chars * +*************************************************/ + +/* gets called when we hit a = + returns: new pointer position + result code in c: + -2 - decode error + -1 - soft line break, no char + 0-255 - char to write +*/ + +uschar *mime_decode_qp(uschar *qp_p,int *c) { + uschar hex[] = {0,0,0}; + int nan = 0; + uschar *initial_pos = qp_p; + + /* advance one char */ + qp_p++; + + REPEAT_FIRST: + if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { + /* tab or whitespace may follow + just ignore it, but remember + that this is not a valid hex + encoding any more */ + nan = 1; + qp_p++; + goto REPEAT_FIRST; + } + else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + /* this is a valid hex char, if nan is unset */ + if (nan) { + /* this is illegal */ + *c = -2; + return initial_pos; + } + else { + hex[0] = *qp_p; + qp_p++; + }; + } + else if (*qp_p == '\n') { + /* hit soft line break already, continue */ + *c = -1; + return qp_p; + } + else { + /* illegal char here */ + *c = -2; + return initial_pos; + }; + + if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + if (hex[0] > 0) { + hex[1] = *qp_p; + /* do hex conversion */ + *c = mime_hstr_i(hex); + qp_p++; + return qp_p; + } + else { + /* huh ? */ + *c = -2; + return initial_pos; + }; + } + else { + /* illegal char */ + *c = -2; + return initial_pos; + }; + +} + + +/************************************************* +* open new dump file * +*************************************************/ + +/* open new dump file + returns: -2 soft error + or file #, FILE * in f +*/ + +int mime_get_dump_file(uschar *extension, FILE **f, uschar *info) { + uschar file_name[1024]; + int result; + unsigned int file_nr; + uschar default_extension[] = ".com"; + uschar *p; + + if (extension == NULL) + extension = default_extension; + + /* scan the proposed extension. + if it is longer than 4 chars, or + contains exotic chars, use the default extension */ + +/* if (Ustrlen(extension) > 4) { + extension = default_extension; + }; +*/ + + p = extension+1; + + while (*p != 0) { + *p = (uschar)tolower((uschar)*p); + if ( (*p < 97) || (*p > 122) ) { + extension = default_extension; + break; + }; + p++; + }; + + /* find a new file to write to */ + file_nr = 0; + do { + struct stat mystat; + + snprintf(CS file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension); + file_nr++; + if (file_nr >= MIME_SANITY_MAX_DUMP_FILES) { + /* max parts reached */ + mime_trigger_error(MIME_ERRORLEVEL_TOO_MANY_PARTS); + break; + }; + result = stat(CS file_name,&mystat); + } + while(result != -1); + + *f = fopen(CS file_name,"w+"); + if (*f == NULL) { + /* cannot open new dump file, disk full ? -> soft error */ + snprintf(CS info, 1024,"unable to open dump file"); + return -2; + }; + + return file_nr; +} + + +/************************************************* +* Find a string in a mime header * +*************************************************/ + +/* Find a string in a mime header, and optionally fill in + the value associated with it into *value + + returns: 0 - nothing found + 1 - found param + 2 - found param + value +*/ + +int mime_header_find(uschar *header, uschar *param, uschar **value) { + uschar *needle; + + needle = strstric(header,param,FALSE); + if (needle != NULL) { + if (value != NULL) { + needle += Ustrlen(param); + if (*needle == '=') { + uschar *value_start; + uschar *value_end; + + value_start = needle + 1; + value_end = strstric(value_start,US";",FALSE); + if (value_end != NULL) { + /* allocate mem for value */ + *value = (uschar *)malloc((value_end - value_start)+1); + if (*value == NULL) + return 0; + + Ustrncpy(*value,value_start,(value_end - value_start)); + (*value)[(value_end - value_start)] = '\0'; + return 2; + }; + }; + }; + return 1; + }; + return 0; +} + + +/************************************************* +* Read a line of MIME input * +*************************************************/ +/* returns status code, one of + MIME_READ_LINE_EOF 0 + MIME_READ_LINE_OK 1 + MIME_READ_LINE_OVERFLOW 2 + + In header mode, the line will be "cooked". +*/ + +int mime_read_line(FILE *f, int mime_demux_mode, uschar *buffer, long *num_copied) { + int c = EOF; + int done = 0; + int header_value_mode = 0; + int header_open_brackets = 0; + + *num_copied = 0; + + while(!done) { + + c = fgetc(f); + if (c == EOF) break; + + /* --------- header mode -------------- */ + if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { + + /* always skip CRs */ + if (c == '\r') continue; + + if (c == '\n') { + if ((*num_copied) > 0) { + /* look if next char is '\t' or ' ' */ + c = fgetc(f); + if (c == EOF) break; + if ( (c == '\t') || (c == ' ') ) continue; + ungetc(c,f); + }; + /* end of the header, terminate with ';' */ + c = ';'; + done = 1; + }; + + /* skip control characters */ + if (c < 32) continue; + + /* skip whitespace + tabs */ + if ( (c == ' ') || (c == '\t') ) + continue; + + if (header_value_mode) { + /* --------- value mode ----------- */ + /* skip quotes */ + if (c == '"') continue; + + /* leave value mode on ';' */ + if (c == ';') { + header_value_mode = 0; + }; + /* -------------------------------- */ + } + else { + /* -------- non-value mode -------- */ + if (c == '\\') { + /* quote next char. can be used + to escape brackets. */ + c = fgetc(f); + if (c == EOF) break; + } + else if (c == '(') { + header_open_brackets++; + continue; + } + else if ((c == ')') && header_open_brackets) { + header_open_brackets--; + continue; + } + else if ( (c == '=') && !header_open_brackets ) { + /* enter value mode */ + header_value_mode = 1; + }; + + /* skip chars while we are in a comment */ + if (header_open_brackets > 0) + continue; + /* -------------------------------- */ + }; + } + /* ------------------------------------ */ + else { + /* ----------- non-header mode -------- */ + /* break on '\n' */ + if (c == '\n') + done = 1; + /* ------------------------------------ */ + }; + + /* copy the char to the buffer */ + buffer[*num_copied] = (uschar)c; + /* raise counter */ + (*num_copied)++; + + /* break if buffer is full */ + if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) { + done = 1; + }; + } + + /* 0-terminate */ + buffer[*num_copied] = '\0'; + + if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) + return MIME_READ_LINE_OVERFLOW; + else + if (c == EOF) + return MIME_READ_LINE_EOF; + else + return MIME_READ_LINE_OK; +} + + +/************************************************* +* Check for a MIME boundary * +*************************************************/ + +/* returns: 0 - no boundary found + 1 - start boundary found + 2 - end boundary found +*/ + +int mime_check_boundary(uschar *line, struct boundary *boundaries) { + struct boundary *thisboundary = boundaries; + uschar workbuf[MIME_SANITY_MAX_LINE_LENGTH+1]; + unsigned int i,j=0; + + /* check for '--' first */ + if (Ustrncmp(line,"--",2) == 0) { + + /* strip tab and space */ + for (i = 2; i < Ustrlen(line); i++) { + if ((line[i] != ' ') && (line[i] != '\t')) { + workbuf[j] = line[i]; + j++; + }; + }; + workbuf[j+1]='\0'; + + while(thisboundary != NULL) { + if (Ustrncmp(workbuf,thisboundary->boundary_string,Ustrlen(thisboundary->boundary_string)) == 0) { + if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) { + /* final boundary found */ + return 2; + }; + return 1; + }; + thisboundary = thisboundary->next; + }; + }; + + return 0; +} + + +/************************************************* +* Check for start of a UUENCODE block * +*************************************************/ + +/* returns 0 for no hit, + >0 for hit +*/ + +int mime_check_uu_start(uschar *line, uschar *uu_file_extension, int *has_tnef) { + + if ( (strncmpic(line,US"begin ",6) == 0)) { + uschar *uu_filename = &line[6]; + + /* skip perms, if present */ + Ustrtoul(&line[6],&uu_filename,10); + + /* advance one char */ + uu_filename++; + + /* This should be the filename. + Check if winmail.dat is present, + which indicates TNEF. */ + if (strncmpic(uu_filename,US"winmail.dat",11) == 0) { + *has_tnef = 1; + }; + + /* reverse to dot if present, + copy up to 4 chars for the extension */ + if (Ustrrchr(uu_filename,'.') != NULL) + uu_filename = Ustrrchr(uu_filename,'.'); + + return sscanf(CS uu_filename, "%4[.0-9A-Za-z]",CS uu_file_extension); + } + else { + /* nothing found */ + return 0; + }; +} + + +/************************************************* +* Decode a uu line * +*************************************************/ + +/* returns number of decoded bytes + -2 for soft errors +*/ + +int warned_about_uudec_line_sanity_1 = 0; +int warned_about_uudec_line_sanity_2 = 0; +long uu_decode_line(uschar *line, uschar **data, long line_len, uschar *info) { + uschar *p; + long num_decoded = 0; + uschar tmp_c; + uschar *work; + int uu_decoded_line_len, uu_encoded_line_len; + + /* allocate memory for data and work buffer */ + *data = (uschar *)malloc(line_len); + if (*data == NULL) { + snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len); + return -2; + }; + + work = (uschar *)malloc(line_len); + if (work == NULL) { + snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len); + return -2; + }; + + memcpy(work,line,line_len); + + /* First char is line length + This is microsofts way of getting it. Scary. */ + if (work[0] < 32) { + /* ignore this line */ + return 0; + } + else { + uu_decoded_line_len = uudec[work[0]]; + }; + + p = &work[1]; + + while (*p > 32) { + *p = uudec[*p]; + p++; + }; + + uu_encoded_line_len = (p - &work[1]); + p = &work[1]; + + /* check that resulting line length is a multiple of 4 */ + if ( ( uu_encoded_line_len % 4 ) != 0) { + if (!warned_about_uudec_line_sanity_1) { + mime_trigger_error(MIME_ERRORLEVEL_UU_MISALIGNED); + warned_about_uudec_line_sanity_1 = 1; + }; + return -1; + }; + + /* check that the line length matches */ + if ( ( (((uu_encoded_line_len/4)*3)-2) > uu_decoded_line_len ) || (((uu_encoded_line_len/4)*3) < uu_decoded_line_len) ) { + if (!warned_about_uudec_line_sanity_2) { + mime_trigger_error(MIME_ERRORLEVEL_UU_LINE_LENGTH); + warned_about_uudec_line_sanity_2 = 1; + }; + return -1; + }; + + while ( ((p - &work[1]) < uu_encoded_line_len) && (num_decoded < uu_decoded_line_len)) { + + /* byte 0 ---------------------- */ + if ((p - &work[1] + 1) >= uu_encoded_line_len) { + return 0; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 2; + + tmp_c = *(p+1); + tmp_c >>= 4; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 1 ---------------------- */ + if ((p - &work[1] + 1) >= uu_encoded_line_len) { + return 0; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 4; + + tmp_c = *(p+1); + tmp_c >>= 2; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 2 ---------------------- */ + if ((p - &work[1] + 1) >= uu_encoded_line_len) { + return 0; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 6; + + (*data)[num_decoded] |= *(p+1); + + num_decoded++; + p+=2; + + }; + + return uu_decoded_line_len; +} + + +/************************************************* +* Decode a b64 or qp line * +*************************************************/ + +/* returns number of decoded bytes + -1 for hard errors + -2 for soft errors +*/ + +int warned_about_b64_line_length = 0; +int warned_about_b64_line_sanity = 0; +int warned_about_b64_illegal_char = 0; +int warned_about_qp_line_sanity = 0; +long mime_decode_line(int mime_demux_mode,uschar *line, uschar **data, long max_data_len, uschar *info) { + uschar *p; + long num_decoded = 0; + int offset = 0; + uschar tmp_c; + + /* allocate memory for data */ + *data = (uschar *)malloc(max_data_len); + if (*data == NULL) { + snprintf(CS info, 1024,"unable to allocate %lu bytes",max_data_len); + return -2; + }; + + if (mime_demux_mode == MIME_DEMUX_MODE_BASE64) { + /* ---------------------------------------------- */ + + /* NULL out trailing '\r' and '\n' chars */ + while (Ustrrchr(line,'\r') != NULL) { + *(Ustrrchr(line,'\r')) = '\0'; + }; + while (Ustrrchr(line,'\n') != NULL) { + *(Ustrrchr(line,'\n')) = '\0'; + }; + + /* check maximum base 64 line length */ + if (Ustrlen(line) > MIME_SANITY_MAX_B64_LINE_LENGTH ) { + if (!warned_about_b64_line_length) { + mime_trigger_error(MIME_ERRORLEVEL_B64_LINE_LENGTH); + warned_about_b64_line_length = 1; + }; + }; + + p = line; + offset = 0; + while (*(p+offset) != '\0') { + /* hit illegal char ? */ + if (b64[*(p+offset)] == 128) { + if (!warned_about_b64_illegal_char) { + mime_trigger_error(MIME_ERRORLEVEL_B64_ILLEGAL_CHAR); + warned_about_b64_illegal_char = 1; + }; + offset++; + } + else { + *p = b64[*(p+offset)]; + p++; + }; + }; + *p = 255; + + /* check that resulting line length is a multiple of 4 */ + if ( ( (p - &line[0]) % 4 ) != 0) { + if (!warned_about_b64_line_sanity) { + mime_trigger_error(MIME_ERRORLEVEL_B64_MISALIGNED); + warned_about_b64_line_sanity = 1; + }; + }; + + /* line is translated, start bit shifting */ + p = line; + num_decoded = 0; + + while(*p != 255) { + + /* byte 0 ---------------------- */ + if (*(p+1) == 255) { + break; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 2; + + tmp_c = *(p+1); + tmp_c >>= 4; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 1 ---------------------- */ + if (*(p+1) == 255) { + break; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 4; + + tmp_c = *(p+1); + tmp_c >>= 2; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 2 ---------------------- */ + if (*(p+1) == 255) { + break; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 6; + + (*data)[num_decoded] |= *(p+1); + + num_decoded++; + p+=2; + + }; + return num_decoded; + /* ---------------------------------------------- */ + } + else if (mime_demux_mode == MIME_DEMUX_MODE_QP) { + /* ---------------------------------------------- */ + p = line; + + while (*p != 0) { + if (*p == '=') { + int decode_qp_result; + + p = mime_decode_qp(p,&decode_qp_result); + + if (decode_qp_result == -2) { + /* Error from decoder. p is unchanged. */ + if (!warned_about_qp_line_sanity) { + mime_trigger_error(MIME_ERRORLEVEL_QP_ILLEGAL_CHAR); + warned_about_qp_line_sanity = 1; + }; + (*data)[num_decoded] = '='; + num_decoded++; + p++; + } + else if (decode_qp_result == -1) { + /* End of the line with soft line break. + Bail out. */ + goto QP_RETURN; + } + else if (decode_qp_result >= 0) { + (*data)[num_decoded] = decode_qp_result; + num_decoded++; + }; + } + else { + (*data)[num_decoded] = *p; + num_decoded++; + p++; + }; + }; + QP_RETURN: + return num_decoded; + /* ---------------------------------------------- */ + }; + + return 0; +} + + + +/************************************************* +* Log demime errors and set mime error level * +*************************************************/ + +/* This sets the global demime_reason expansion +variable and the demime_errorlevel gauge. */ + +void mime_trigger_error(int level, uschar *format, ...) { + char *f; + va_list ap; + + if( (f = malloc(16384+23)) != NULL ) { + /* first log the incident */ + sprintf(f,"demime acl condition: "); + f+=22; + va_start(ap, format); + vsnprintf(f, 16383,(char *)format, ap); + va_end(ap); + f-=22; + log_write(0, LOG_MAIN, f); + /* then copy to demime_reason_buffer if new + level is greater than old level */ + if (level > demime_errorlevel) { + demime_errorlevel = level; + Ustrcpy(demime_reason_buffer, US f); + demime_reason = demime_reason_buffer; + }; + free(f); + }; +} + +/************************************************* +* Demultiplex MIME stream. * +*************************************************/ + +/* We can handle BASE64, QUOTED-PRINTABLE, and UUENCODE. + UUENCODE does not need to have a proper + transfer-encoding header, we detect it with "begin" + + This function will report human parsable errors in + *info. + + returns DEFER -> soft error (see *info) + OK -> EOF hit, all ok +*/ + +int mime_demux(FILE *f, uschar *info) { + int mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; + int uu_mode = MIME_UU_MODE_OFF; + FILE *mime_dump_file = NULL; + FILE *uu_dump_file = NULL; + uschar *line; + int mime_read_line_status = MIME_READ_LINE_OK; + long line_len; + struct boundary *boundaries = NULL; + struct mime_part mime_part_p; + int has_tnef = 0; + int has_rfc822 = 0; + + /* allocate room for our linebuffer */ + line = (uschar *)malloc(MIME_SANITY_MAX_LINE_LENGTH); + if (line == NULL) { + snprintf(CS info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH); + return DEFER; + }; + + /* clear MIME header structure */ + memset(&mime_part_p,0,sizeof(mime_part)); + + /* ----------------------- start demux loop --------------------- */ + while (mime_read_line_status == MIME_READ_LINE_OK) { + + /* read a line of input. Depending on the mode we are in, + the returned format will differ. */ + mime_read_line_status = mime_read_line(f,mime_demux_mode,line,&line_len); + + if (mime_read_line_status == MIME_READ_LINE_OVERFLOW) { + mime_trigger_error(MIME_ERRORLEVEL_LONG_LINE); + /* despite the error, continue .. */ + mime_read_line_status = MIME_READ_LINE_OK; + continue; + } + else if (mime_read_line_status == MIME_READ_LINE_EOF) { + break; + }; + + if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { + /* -------------- header mode --------------------- */ + + /* Check for an empty line, which is the end of the headers. + In HEADER mode, the line is returned "cooked", with the + final '\n' replaced by a ';' */ + if (line_len == 1) { + int tmp; + + /* We have reached the end of the headers. Start decoding + with the collected settings. */ + if (mime_part_p.seen_content_transfer_encoding > 1) { + mime_demux_mode = mime_part_p.seen_content_transfer_encoding; + } + else { + /* default to plain mode if no specific encoding type found */ + mime_demux_mode = MIME_DEMUX_MODE_PLAIN; + }; + + /* open new dump file */ + tmp = mime_get_dump_file(mime_part_p.extension, &mime_dump_file, info); + if (tmp < 0) { + return DEFER; + }; + + /* clear out mime_part */ + memset(&mime_part_p,0,sizeof(mime_part)); + } + else { + /* Another header to check for file extensions, + encoding type and boundaries */ + if (strncmpic(US"content-type:",line,Ustrlen("content-type:")) == 0) { + /* ---------------------------- Content-Type header ------------------------------- */ + uschar *value = line; + + /* check for message/partial MIME type and reject it */ + if (mime_header_find(line,US"message/partial",NULL) > 0) + mime_trigger_error(MIME_ERRORLEVEL_MESSAGE_PARTIAL); + + /* check for TNEF content type, remember to unpack TNEF later. */ + if (mime_header_find(line,US"application/ms-tnef",NULL) > 0) + has_tnef = 1; + + /* check for message/rfcxxx attachments */ + if (mime_header_find(line,US"message/rfc822",NULL) > 0) + has_rfc822 = 1; + + /* find the file extension, but do not fill it in + it is already set, since content-disposition has + precedence. */ + if (mime_part_p.extension == NULL) { + if (mime_header_find(line,US"name",&value) == 2) { + if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) + mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); + mime_part_p.extension = value; + mime_part_p.extension = Ustrrchr(value,'.'); + if (mime_part_p.extension == NULL) { + /* file without extension, setting + NULL will use the default extension later */ + mime_part_p.extension = NULL; + } + else { + struct file_extension *this_extension = + (struct file_extension *)malloc(sizeof(file_extension)); + + this_extension->file_extension_string = + (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); + Ustrcpy(this_extension->file_extension_string, + mime_part_p.extension+1); + this_extension->next = file_extensions; + file_extensions = this_extension; + }; + }; + }; + + /* find a boundary and add it to the list, if present */ + value = line; + if (mime_header_find(line,US"boundary",&value) == 2) { + struct boundary *thisboundary; + + if (Ustrlen(value) > MIME_SANITY_MAX_BOUNDARY_LENGTH) { + mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH); + } + else { + thisboundary = (struct boundary*)malloc(sizeof(boundary)); + thisboundary->next = boundaries; + thisboundary->boundary_string = value; + boundaries = thisboundary; + }; + }; + + if (mime_part_p.seen_content_type == 0) { + mime_part_p.seen_content_type = 1; + } + else { + mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); + }; + /* ---------------------------------------------------------------------------- */ + } + else if (strncmpic(US"content-transfer-encoding:",line,Ustrlen("content-transfer-encoding:")) == 0) { + /* ---------------------------- Content-Transfer-Encoding header -------------- */ + + if (mime_part_p.seen_content_transfer_encoding == 0) { + if (mime_header_find(line,US"base64",NULL) > 0) { + mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_BASE64; + } + else if (mime_header_find(line,US"quoted-printable",NULL) > 0) { + mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_QP; + } + else { + mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_PLAIN; + }; + } + else { + mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); + }; + /* ---------------------------------------------------------------------------- */ + } + else if (strncmpic(US"content-disposition:",line,Ustrlen("content-disposition:")) == 0) { + /* ---------------------------- Content-Disposition header -------------------- */ + uschar *value = line; + + if (mime_part_p.seen_content_disposition == 0) { + mime_part_p.seen_content_disposition = 1; + + if (mime_header_find(line,US"filename",&value) == 2) { + if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) + mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); + mime_part_p.extension = value; + mime_part_p.extension = Ustrrchr(value,'.'); + if (mime_part_p.extension == NULL) { + /* file without extension, setting + NULL will use the default extension later */ + mime_part_p.extension = NULL; + } + else { + struct file_extension *this_extension = + (struct file_extension *)malloc(sizeof(file_extension)); + + this_extension->file_extension_string = + (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); + Ustrcpy(this_extension->file_extension_string, + mime_part_p.extension+1); + this_extension->next = file_extensions; + file_extensions = this_extension; + }; + }; + } + else { + mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); + }; + /* ---------------------------------------------------------------------------- */ + }; + }; /* End of header checks */ + /* ------------------------------------------------ */ + } + else { + /* -------------- non-header mode ----------------- */ + int tmp; + + if (uu_mode == MIME_UU_MODE_OFF) { + uschar uu_file_extension[5]; + /* We are not currently decoding UUENCODE + Check for possible UUENCODE start tag. */ + if (mime_check_uu_start(line,uu_file_extension,&has_tnef)) { + /* possible UUENCODING start detected. + Set unconfirmed mode first. */ + uu_mode = MIME_UU_MODE_UNCONFIRMED; + /* open new uu dump file */ + tmp = mime_get_dump_file(uu_file_extension, &uu_dump_file, info); + if (tmp < 0) { + free(line); + return DEFER; + }; + }; + } + else { + uschar *data; + long data_len = 0; + + if (uu_mode == MIME_UU_MODE_UNCONFIRMED) { + /* We are in unconfirmed UUENCODE mode. */ + + data_len = uu_decode_line(line,&data,line_len,info); + + if (data_len == -2) { + /* temp error, turn off uudecode mode */ + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + return DEFER; + } + else if (data_len == -1) { + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + data_len = 0; + } + else if (data_len > 0) { + /* we have at least decoded a valid byte + turn on confirmed mode */ + uu_mode = MIME_UU_MODE_CONFIRMED; + }; + } + else if (uu_mode == MIME_UU_MODE_CONFIRMED) { + /* If we are in confirmed UU mode, + check for single "end" tag on line */ + if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) { + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + } + else { + data_len = uu_decode_line(line,&data,line_len,info); + if (data_len == -2) { + /* temp error, turn off uudecode mode */ + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + return DEFER; + } + else if (data_len == -1) { + /* skip this line */ + data_len = 0; + }; + }; + }; + + /* write data to dump file, if available */ + if (data_len > 0) { + if (fwrite(data,1,data_len,uu_dump_file) < data_len) { + /* short write */ + snprintf(CS info, 1024,"short write on uudecode dump file"); + free(line); + return DEFER; + }; + }; + }; + + if (mime_demux_mode != MIME_DEMUX_MODE_SCANNING) { + /* Non-scanning and Non-header mode. That means + we are currently decoding data to the dump + file. */ + + /* Check for a known boundary. */ + tmp = mime_check_boundary(line,boundaries); + if (tmp == 1) { + /* We have hit a known start boundary. + That will put us back in header mode. */ + mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; + if (mime_dump_file != NULL) { + /* if the attachment was a RFC822 message, recurse into it */ + if (has_rfc822) { + has_rfc822 = 0; + rewind(mime_dump_file); + mime_demux(mime_dump_file,info); + }; + + fclose(mime_dump_file); mime_dump_file = NULL; + }; + } + else if (tmp == 2) { + /* We have hit a known end boundary. + That puts us into scanning mode, which will end when we hit another known start boundary */ + mime_demux_mode = MIME_DEMUX_MODE_SCANNING; + if (mime_dump_file != NULL) { + /* if the attachment was a RFC822 message, recurse into it */ + if (has_rfc822) { + has_rfc822 = 0; + rewind(mime_dump_file); + mime_demux(mime_dump_file,info); + }; + + fclose(mime_dump_file); mime_dump_file = NULL; + }; + } + else { + uschar *data; + long data_len = 0; + + /* decode the line with the appropriate method */ + if (mime_demux_mode == MIME_DEMUX_MODE_PLAIN) { + /* in plain mode, just dump the line */ + data = line; + data_len = line_len; + } + else if ( (mime_demux_mode == MIME_DEMUX_MODE_QP) || (mime_demux_mode == MIME_DEMUX_MODE_BASE64) ) { + data_len = mime_decode_line(mime_demux_mode,line,&data,line_len,info); + if (data_len < 0) { + /* Error reported from the line decoder. */ + data_len = 0; + }; + }; + + /* write data to dump file */ + if (data_len > 0) { + if (fwrite(data,1,data_len,mime_dump_file) < data_len) { + /* short write */ + snprintf(CS info, 1024,"short write on dump file"); + free(line); + return DEFER; + }; + }; + + }; + } + else { + /* Scanning mode. We end up here after a end boundary. + This will usually be at the end of a message or at + the end of a MIME container. + We need to look for another start boundary to get + back into header mode. */ + if (mime_check_boundary(line,boundaries) == 1) { + mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; + }; + + }; + /* ------------------------------------------------ */ + }; + }; + /* ----------------------- end demux loop ----------------------- */ + + /* close files, they could still be open */ + if (mime_dump_file != NULL) + fclose(mime_dump_file); + if (uu_dump_file != NULL) + fclose(uu_dump_file); + + /* release line buffer */ + free(line); + + /* FIXME: release boundary buffers. + Not too much of a problem since + this instance of exim is not resident. */ + + if (has_tnef) { + uschar file_name[1024]; + /* at least one file could be TNEF encoded. + attempt to send all decoded files thru the TNEF decoder */ + + snprintf(CS file_name,1024,"%s/scan/%s",spool_directory,message_id); + mime_unpack_tnef(file_name); + }; + + return 0; +} + diff -urN exim-4.41-orig/src/demime.h exim-4.41/src/demime.h --- exim-4.41-orig/src/demime.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/demime.h Thu Jul 22 16:31:13 2004 @@ -0,0 +1,146 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* demime defines */ + +#define MIME_DEMUX_MODE_SCANNING 0 +#define MIME_DEMUX_MODE_MIME_HEADERS 1 +#define MIME_DEMUX_MODE_BASE64 2 +#define MIME_DEMUX_MODE_QP 3 +#define MIME_DEMUX_MODE_PLAIN 4 + +#define MIME_UU_MODE_OFF 0 +#define MIME_UU_MODE_UNCONFIRMED 1 +#define MIME_UU_MODE_CONFIRMED 2 + +#define MIME_MAX_EXTENSION 128 + +#define MIME_READ_LINE_EOF 0 +#define MIME_READ_LINE_OK 1 +#define MIME_READ_LINE_OVERFLOW 2 + +#define MIME_SANITY_MAX_LINE_LENGTH 131071 +#define MIME_SANITY_MAX_FILENAME 512 +#define MIME_SANITY_MAX_HEADER_OPTION_VALUE 1024 +#define MIME_SANITY_MAX_B64_LINE_LENGTH 76 +#define MIME_SANITY_MAX_BOUNDARY_LENGTH 1024 +#define MIME_SANITY_MAX_DUMP_FILES 1024 + + + +/* MIME errorlevel settings */ + +#define MIME_ERRORLEVEL_LONG_LINE 3,US"line length in message or single header size exceeds %u bytes",MIME_SANITY_MAX_LINE_LENGTH +#define MIME_ERRORLEVEL_TOO_MANY_PARTS 3,US"too many MIME parts (max %u)",MIME_SANITY_MAX_DUMP_FILES +#define MIME_ERRORLEVEL_MESSAGE_PARTIAL 3,US"'message/partial' MIME type" +#define MIME_ERRORLEVEL_FILENAME_LENGTH 3,US"proposed filename exceeds %u characters",MIME_SANITY_MAX_FILENAME +#define MIME_ERRORLEVEL_BOUNDARY_LENGTH 3,US"boundary length exceeds %u characters",MIME_SANITY_MAX_BOUNDARY_LENGTH +#define MIME_ERRORLEVEL_DOUBLE_HEADERS 2,US"double headers (content-type, content-disposition or content-transfer-encoding)" +#define MIME_ERRORLEVEL_UU_MISALIGNED 1,US"uuencoded line length is not a multiple of 4 characters" +#define MIME_ERRORLEVEL_UU_LINE_LENGTH 1,US"uuencoded line length does not match advertised number of bytes" +#define MIME_ERRORLEVEL_B64_LINE_LENGTH 1,US"base64 line length exceeds %u characters",MIME_SANITY_MAX_B64_LINE_LENGTH +#define MIME_ERRORLEVEL_B64_ILLEGAL_CHAR 2,US"base64 line contains illegal character" +#define MIME_ERRORLEVEL_B64_MISALIGNED 1,US"base64 line length is not a multiple of 4 characters" +#define MIME_ERRORLEVEL_QP_ILLEGAL_CHAR 1,US"quoted-printable encoding contains illegal character" + + +/* demime structures */ + +typedef struct mime_part { + /* true if there was a content-type header */ + int seen_content_type; + /* true if there was a content-transfer-encoding header + contains the encoding type */ + int seen_content_transfer_encoding; + /* true if there was a content-disposition header */ + int seen_content_disposition; + /* pointer to a buffer with the proposed file extension */ + uschar *extension; +} mime_part; + +typedef struct boundary { + struct boundary *next; + uschar *boundary_string; +} boundary; + +typedef struct file_extension { + struct file_extension *next; + uschar *file_extension_string; +} file_extension; + +/* available functions for the TNEF library (tnef.c & tnef.h) */ + +extern int TNEF_main( char *filename ); +extern int TNEF_set_verbosity( int level ); +extern int TNEF_set_debug( int level ); +extern int TNEF_set_syslogging( int level ); +extern int TNEF_set_stderrlogging( int level ); +extern int TNEF_set_path( char *path ); + + + +/* demime.c prototypes */ + +int mime_unpack_tnef(uschar *); +unsigned int mime_hstr_i(uschar *); +uschar *mime_decode_qp(uschar *, int *); +int mime_get_dump_file(uschar *, FILE **, uschar *); +int mime_header_find(uschar *, uschar *, uschar **); +int mime_read_line(FILE *, int, uschar *, long *); +int mime_check_boundary(uschar *, struct boundary *); +int mime_check_uu_start(uschar *, uschar *, int *); +long uu_decode_line(uschar *, uschar **, long, uschar *); +long mime_decode_line(int ,uschar *, uschar **, long, uschar *); +void mime_trigger_error(int, uschar *, ...); +int mime_demux(FILE *, uschar *); + + + +/* BASE64 decoder matrix */ +static unsigned char b64[256]={ +/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, +/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128, +/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128, +/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 +}; + + +/* Microsoft-Style uudecode matrix */ +static unsigned char uudec[256]={ +/* 0 */ 0, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 16 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 32 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 48 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +/* 64 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 80 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 96 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 112 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +/* 128 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 144 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 160 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 176 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +/* 192 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 208 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 224 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 240 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 +}; + diff -urN exim-4.41-orig/src/exim.c exim-4.41/src/exim.c --- exim-4.41-orig/src/exim.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/exim.c Thu Jul 22 16:31:13 2004 @@ -1542,6 +1542,10 @@ printf("%s\n", CS version_copyright); version_printed = TRUE; show_whats_supported(stdout); + printf("Contains exiscan-acl patch revision %s (c) Tom Kistner [http://duncanthrax.net/exiscan/]\n", exiscan_version_string); +#ifdef BRIGHTMAIL + printf("Contains Brightmail AntiSpam support via the exiscan-acl patch.\n"); +#endif } else badarg = TRUE; diff -urN exim-4.41-orig/src/expand.c exim-4.41/src/expand.c --- exim-4.41-orig/src/expand.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/expand.c Thu Jul 22 16:31:13 2004 @@ -312,6 +312,12 @@ { "authenticated_id", vtype_stringptr, &authenticated_id }, { "authenticated_sender",vtype_stringptr, &authenticated_sender }, { "authentication_failed",vtype_int, &authentication_failed }, +#ifdef BRIGHTMAIL + { "bmi_alt_location", vtype_stringptr, &bmi_alt_location }, + { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict }, + { "bmi_base64_verdict", vtype_stringptr, &bmi_base64_verdict }, + { "bmi_deliver", vtype_int, &bmi_deliver }, +#endif { "body_linecount", vtype_int, &body_linecount }, { "body_zerocount", vtype_int, &body_zerocount }, { "bounce_recipient", vtype_stringptr, &bounce_recipient }, @@ -320,6 +326,8 @@ { "caller_uid", vtype_uid, &real_uid }, { "compile_date", vtype_stringptr, &version_date }, { "compile_number", vtype_stringptr, &version_cnumber }, + { "demime_errorlevel", vtype_int, &demime_errorlevel }, + { "demime_reason", vtype_stringptr, &demime_reason }, { "dnslist_domain", vtype_stringptr, &dnslist_domain }, { "dnslist_text", vtype_stringptr, &dnslist_text }, { "dnslist_value", vtype_stringptr, &dnslist_value }, @@ -328,6 +336,7 @@ { "exim_gid", vtype_gid, &exim_gid }, { "exim_path", vtype_stringptr, &exim_path }, { "exim_uid", vtype_uid, &exim_uid }, + { "found_extension", vtype_stringptr, &found_extension }, { "home", vtype_stringptr, &deliver_home }, { "host", vtype_stringptr, &deliver_host }, { "host_address", vtype_stringptr, &deliver_host_address }, @@ -349,6 +358,7 @@ { "local_user_uid", vtype_uid, &local_user_uid }, { "localhost_number", vtype_int, &host_number }, { "mailstore_basename", vtype_stringptr, &mailstore_basename }, + { "malware_name", vtype_stringptr, &malware_name }, { "message_age", vtype_int, &message_age }, { "message_body", vtype_msgbody, &message_body }, { "message_body_end", vtype_msgbody_end, &message_body_end }, @@ -356,6 +366,22 @@ { "message_headers", vtype_msgheaders, NULL }, { "message_id", vtype_stringptr, &message_id }, { "message_size", vtype_int, &message_size }, + { "mime_anomaly_level", vtype_int, &mime_anomaly_level }, + { "mime_anomaly_text", vtype_stringptr, &mime_anomaly_text }, + { "mime_boundary", vtype_stringptr, &mime_boundary }, + { "mime_charset", vtype_stringptr, &mime_charset }, + { "mime_content_description", vtype_stringptr, &mime_content_description }, + { "mime_content_disposition", vtype_stringptr, &mime_content_disposition }, + { "mime_content_id", vtype_stringptr, &mime_content_id }, + { "mime_content_size", vtype_int, &mime_content_size }, + { "mime_content_transfer_encoding",vtype_stringptr, &mime_content_transfer_encoding }, + { "mime_content_type", vtype_stringptr, &mime_content_type }, + { "mime_decoded_filename", vtype_stringptr, &mime_decoded_filename }, + { "mime_filename", vtype_stringptr, &mime_filename }, + { "mime_is_coverletter", vtype_int, &mime_is_coverletter }, + { "mime_is_multipart", vtype_int, &mime_is_multipart }, + { "mime_is_rfc822", vtype_int, &mime_is_rfc822 }, + { "mime_part_count", vtype_int, &mime_part_count }, { "n0", vtype_filter_int, &filter_n[0] }, { "n1", vtype_filter_int, &filter_n[1] }, { "n2", vtype_filter_int, &filter_n[2] }, @@ -385,6 +411,7 @@ { "recipient_data", vtype_stringptr, &recipient_data }, { "recipients", vtype_recipients, NULL }, { "recipients_count", vtype_int, &recipients_count }, + { "regex_match_string", vtype_stringptr, ®ex_match_string }, { "reply_address", vtype_reply, NULL }, { "return_path", vtype_stringptr, &return_path }, { "return_size_limit", vtype_int, &bounce_return_size_limit }, @@ -413,7 +440,25 @@ { "sn7", vtype_filter_int, &filter_sn[7] }, { "sn8", vtype_filter_int, &filter_sn[8] }, { "sn9", vtype_filter_int, &filter_sn[9] }, + { "spam_bar", vtype_stringptr, &spam_bar }, + { "spam_report", vtype_stringptr, &spam_report }, + { "spam_score", vtype_stringptr, &spam_score }, + { "spam_score_int", vtype_stringptr, &spam_score_int }, +#ifdef SPF + { "spf_header_comment", vtype_stringptr, &spf_header_comment }, + { "spf_received", vtype_stringptr, &spf_received }, + { "spf_result", vtype_stringptr, &spf_result }, + { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment }, +#endif { "spool_directory", vtype_stringptr, &spool_directory }, +#ifdef SRS + { "srs_db_address", vtype_stringptr, &srs_db_address }, + { "srs_db_key", vtype_stringptr, &srs_db_key }, + { "srs_orig_recipient", vtype_stringptr, &srs_orig_recipient }, + { "srs_orig_sender", vtype_stringptr, &srs_orig_sender }, + { "srs_recipient", vtype_stringptr, &srs_recipient }, + { "srs_status", vtype_stringptr, &srs_status }, +#endif { "thisaddress", vtype_stringptr, &filter_thisaddress }, { "tls_certificate_verified", vtype_int, &tls_certificate_verified }, { "tls_cipher", vtype_stringptr, &tls_cipher }, diff -urN exim-4.41-orig/src/functions.h exim-4.41/src/functions.h --- exim-4.41-orig/src/functions.h Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/functions.h Wed Aug 18 15:00:38 2004 @@ -66,6 +66,7 @@ extern void deliver_set_expansions(address_item *); extern int deliver_split_address(address_item *); extern void deliver_succeeded(address_item *); +extern int demime(uschar **); extern BOOL directory_make(uschar *, uschar *, int, BOOL); extern dns_address *dns_address_from_rr(dns_answer *, dns_record *); extern void dns_build_reverse(uschar *, uschar *); @@ -92,6 +93,7 @@ extern BOOL filter_system_interpret(address_item **, uschar **); extern void header_add(int, char *, ...); +extern header_line *header_insert(header_line *); extern int header_checkname(header_line *, BOOL); extern uschar *host_and_ident(BOOL); extern int host_aton(uschar *, int *); @@ -119,6 +121,7 @@ extern void log_close_all(void); +extern int malware(uschar **); extern int match_address_list(uschar *, BOOL, BOOL, uschar **, unsigned int *, int, int, uschar **); extern int match_check_list(uschar **, int, tree_node **, unsigned int **, @@ -132,6 +135,11 @@ extern void md5_mid(md5 *, const uschar *); extern void md5_start(md5 *); extern void millisleep(int); +struct mime_boundary_context; +extern int mime_acl_check(FILE *f, struct mime_boundary_context *, + uschar **, uschar **); +extern int mime_decode(uschar **); +extern int mime_regex(uschar **); extern uschar *moan_check_errorcopy(uschar *); extern BOOL moan_skipped_syntax_errors(uschar *, error_block *, uschar *, BOOL, uschar *); @@ -156,6 +164,7 @@ extern void queue_run(uschar *, uschar *, BOOL); extern int random_number(int); +extern int recv_line(int, uschar *, int); extern int rda_interpret(redirect_block *, int, uschar *, ugid_block *, address_item **, uschar **, error_block **, int *, uschar *); extern int rda_is_filter(const uschar *); @@ -175,6 +184,7 @@ extern BOOL receive_check_set_sender(uschar *); extern BOOL receive_msg(BOOL); extern void receive_swallow_smtp(void); +extern int regex(uschar **); extern BOOL regex_match_and_setup(const pcre *, uschar *, int, int); extern const pcre *regex_must_compile(uschar *, BOOL, BOOL); extern void retry_add_item(address_item *, uschar *, int); @@ -234,6 +244,8 @@ extern BOOL smtp_start_session(void); extern int smtp_ungetc(int); extern int smtp_write_command(smtp_outblock *, BOOL, char *, ...); +extern int spam(uschar **); +extern FILE *spool_mbox(unsigned long long *); extern BOOL spool_move_message(uschar *, uschar *, uschar *, uschar *); extern BOOL spool_open_datafile(uschar *); extern int spool_open_temp(uschar *); @@ -285,6 +297,8 @@ extern tree_node *tree_search(tree_node *, uschar *); extern void tree_write(tree_node *, FILE *); +extern void unspool_mbox(void); + extern int verify_address(address_item *, FILE *, int, int, BOOL *); extern int verify_check_dnsbl(uschar **); extern int verify_check_header_address(uschar **, uschar **, int); diff -urN exim-4.41-orig/src/globals.c exim-4.41/src/globals.c --- exim-4.41-orig/src/globals.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/globals.c Mon Aug 2 13:35:27 2004 @@ -152,6 +152,7 @@ /* General global variables */ tree_node *acl_anchor = NULL; +int acl_header_pos = 0; uschar *acl_not_smtp = NULL; uschar *acl_smtp_auth = NULL; uschar *acl_smtp_connect = NULL; @@ -161,6 +162,7 @@ uschar *acl_smtp_helo = NULL; uschar *acl_smtp_mail = NULL; uschar *acl_smtp_mailauth = NULL; +uschar *acl_smtp_mime = NULL; uschar *acl_smtp_rcpt = NULL; uschar *acl_smtp_starttls = NULL; uschar *acl_smtp_vrfy = NULL; @@ -181,6 +183,7 @@ US"EHLO or HELO", US"MAIL", US"MAILAUTH", + US"MIME", US"RCPT", US"STARTTLS", US"VRFY", @@ -194,6 +197,7 @@ 550, /* HELO/EHLO */ 550, /* MAIL */ 0, /* MAILAUTH; not relevant */ + 550, /* MIME */ 550, /* RCPT */ 550, /* STARTTLS */ 252, /* VRFY */ @@ -296,6 +300,7 @@ uschar *auth_defer_msg = US"reason not recorded"; uschar *auth_defer_user_msg = US""; int auto_thaw = 0; +uschar *av_scanner = US"sophie:/var/run/sophie"; BOOL background_daemon = TRUE; uschar *base62_chars= @@ -303,6 +308,15 @@ uschar *bi_command = NULL; uschar *big_buffer = NULL; int big_buffer_size = BIG_BUFFER_SIZE; +#ifdef BRIGHTMAIL +uschar *bmi_alt_location = NULL; +uschar *bmi_base64_tracker_verdict = NULL; +uschar *bmi_base64_verdict = NULL; +uschar *bmi_config_file = US"/opt/brightmail/etc/brightmail.cfg"; +int bmi_deliver = 1; +int bmi_run = 0; +uschar *bmi_verdicts = NULL; +#endif int body_linecount = 0; int body_zerocount = 0; uschar *bounce_message_file = NULL; @@ -418,6 +432,9 @@ BOOL deliver_selectstring_regex = FALSE; uschar *deliver_selectstring_sender = NULL; BOOL deliver_selectstring_sender_regex = FALSE; +int demime_errorlevel = 0; +int demime_ok = 0; +uschar *demime_reason = NULL; BOOL disable_logging = FALSE; uschar *dns_again_means_nonexist = NULL; @@ -447,6 +464,7 @@ "\0<---------------Space to patch exim_path->"; uid_t exim_uid = EXIM_UID; BOOL exim_uid_set = TRUE; /* This uid is always set */ +uschar *exiscan_version_string = US"??"; int expand_forbid = 0; int expand_nlength[EXPAND_MAXN+1]; int expand_nmax = -1; @@ -456,12 +474,14 @@ BOOL extract_addresses_remove_arguments = TRUE; uschar *extra_local_interfaces = NULL; +BOOL fake_reject = FALSE; int filter_n[FILTER_VARIABLE_COUNT]; BOOL filter_running = FALSE; int filter_sn[FILTER_VARIABLE_COUNT]; uschar *filter_test = NULL; uschar *filter_thisaddress = NULL; int finduser_retries = 0; +uschar *found_extension = NULL; uid_t fixed_never_users[] = { FIXED_NEVER_USERS }; uschar *freeze_tell = NULL; uschar *fudged_queue_times = US""; @@ -615,6 +635,7 @@ macro_item *macros = NULL; uschar *mailstore_basename = NULL; +uschar *malware_name = NULL; int max_username_length = 0; int message_age = 0; uschar *message_body = NULL; @@ -635,8 +656,25 @@ uschar *message_size_limit = US"50M"; uschar message_subdir[2] = { 0, 0 }; uschar *message_reference = NULL; +uschar *mime_anomaly_level = NULL; +uschar *mime_anomaly_text = NULL; +uschar *mime_boundary = NULL; +uschar *mime_charset = NULL; +uschar *mime_content_description = NULL; +uschar *mime_content_disposition = NULL; +uschar *mime_content_id = NULL; +unsigned int mime_content_size = 0; +uschar *mime_content_transfer_encoding = NULL; +uschar *mime_content_type = NULL; +uschar *mime_decoded_filename = NULL; +uschar *mime_filename = NULL; +int mime_is_multipart = 0; +int mime_is_coverletter = 0; +int mime_is_rfc822 = 0; +int mime_part_count = -1; uid_t *never_users = NULL; +BOOL no_mbox_unspool = FALSE; uid_t original_euid; gid_t originator_gid; @@ -735,6 +773,7 @@ const pcre *regex_PIPELINING = NULL; const pcre *regex_SIZE = NULL; const pcre *regex_ismsgid = NULL; +uschar *regex_match_string = NULL; int remote_delivery_count = 0; int remote_max_parallel = 2; uschar *remote_sort_domains = NULL; @@ -759,6 +798,9 @@ NULL, /* driver name */ NULL, /* address_data */ +#ifdef BRIGHTMAIL + NULL, /* bmi_rule */ +#endif NULL, /* cannot_route_message */ NULL, /* condition */ NULL, /* current_directory */ @@ -787,6 +829,11 @@ NULL, /* transport_name */ TRUE, /* address_test */ +#ifdef BRIGHTMAIL + FALSE, /* bmi_deliver_alternate */ + FALSE, /* bmi_deliver_default */ + FALSE, /* bmi_dont_deliver */ +#endif TRUE, /* expn */ FALSE, /* caseful_local_part */ FALSE, /* check_local_user */ @@ -913,9 +960,29 @@ int smtp_rlr_threshold = INT_MAX; BOOL smtp_use_pipelining = FALSE; BOOL smtp_use_size = FALSE; +uschar *spamd_address = US"127.0.0.1 783"; +uschar *spam_bar = NULL; +uschar *spam_report = NULL; +uschar *spam_score = NULL; +uschar *spam_score_int = NULL; +#ifdef SPF +uschar *spf_header_comment = NULL; +uschar *spf_received = NULL; +uschar *spf_result = NULL; +uschar *spf_smtp_comment = NULL; +#endif BOOL split_spool_directory = FALSE; uschar *spool_directory = US SPOOL_DIRECTORY "\0<--------------Space to patch spool_directory->"; +#ifdef SRS +uschar *srs_config = NULL; +uschar *srs_db_address = NULL; +uschar *srs_db_key = NULL; +uschar *srs_orig_recipient = NULL; +uschar *srs_orig_sender = NULL; +uschar *srs_recipient = NULL; +uschar *srs_status = NULL; +#endif int string_datestamp_offset= -1; BOOL strip_excess_angle_brackets = FALSE; BOOL strip_trailing_dot = FALSE; diff -urN exim-4.41-orig/src/globals.h exim-4.41/src/globals.h --- exim-4.41-orig/src/globals.h Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/globals.h Mon Aug 2 13:35:06 2004 @@ -94,6 +94,7 @@ extern BOOL accept_8bitmime; /* Allow *BITMIME incoming */ extern tree_node *acl_anchor; /* Tree of named ACLs */ +extern int acl_header_pos; /* Where to put headers in MAIL, RCPT, DATA, MIME and non-SMTP ACLs */ extern uschar *acl_not_smtp; /* ACL run for non-SMTP messages */ extern uschar *acl_smtp_auth; /* ACL run after AUTH */ extern uschar *acl_smtp_connect; /* ACL run on SMTP connection */ @@ -103,6 +104,7 @@ extern uschar *acl_smtp_helo; /* ACL run after HELO/EHLO */ extern uschar *acl_smtp_mail; /* ACL run after MAIL */ extern uschar *acl_smtp_mailauth; /* ACL run after MAIL AUTH */ +extern uschar *acl_smtp_mime; /* ACL run after DATA, before acl_smtp_data, for each MIME part */ extern uschar *acl_smtp_rcpt; /* ACL run after RCPT */ extern uschar *acl_smtp_starttls; /* ACL run after STARTTLS */ extern uschar *acl_smtp_vrfy; /* ACL run after VRFY */ @@ -137,12 +139,22 @@ extern uschar *auth_defer_msg; /* Error message for log */ extern uschar *auth_defer_user_msg; /* Error message for user */ extern int auto_thaw; /* Auto-thaw interval */ +extern uschar *av_scanner; /* AntiVirus scanner to use for the malware condition */ extern BOOL background_daemon; /* Set FALSE to keep in foreground */ extern uschar *base62_chars; /* Table of base-62 characters */ extern uschar *bi_command; /* Command for -bi option */ extern uschar *big_buffer; /* Used for various temp things */ extern int big_buffer_size; /* Current size (can expand) */ +#ifdef BRIGHTMAIL +extern uschar *bmi_alt_location; /* expansion variable that contains the alternate location for the rcpt (available during routing) */ +extern uschar *bmi_base64_tracker_verdict; /* expansion variable with base-64 encoded OLD verdict string (available during routing) */ +extern uschar *bmi_base64_verdict; /* expansion variable with base-64 encoded verdict string (available during routing) */ +extern uschar *bmi_config_file; /* Brightmail config file */ +extern int bmi_deliver; /* Flag that determines if the message should be delivered to the rcpt (available during routing) */ +extern int bmi_run; /* Flag that determines if message should be run through Brightmail server */ +extern uschar *bmi_verdicts; /* BASE64-encoded verdicts with recipient lists */ +#endif extern int body_linecount; /* Line count in body */ extern int body_zerocount; /* Binary zero count in body */ extern uschar *bounce_message_file; /* Template file */ @@ -224,6 +236,9 @@ extern BOOL deliver_selectstring_regex; /* String is regex */ extern uschar *deliver_selectstring_sender; /* For selecting by sender */ extern BOOL deliver_selectstring_sender_regex; /* String is regex */ +extern int demime_errorlevel; /* Severity of MIME error */ +extern int demime_ok; /* Nonzero if message has been demimed */ +extern uschar *demime_reason; /* Reason for broken MIME container */ extern BOOL disable_logging; /* Disables log writing when TRUE */ extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */ @@ -253,6 +268,7 @@ extern uschar *exim_path; /* Path to exec exim */ extern uid_t exim_uid; /* Non-root uid for exim */ extern BOOL exim_uid_set; /* TRUE if exim_uid set */ +extern uschar *exiscan_version_string; /* Exiscan version string */ extern int expand_forbid; /* RDO flags for forbidding things */ extern int expand_nlength[]; /* Lengths of numbered strings */ extern int expand_nmax; /* Max numerical value */ @@ -261,6 +277,7 @@ extern BOOL extract_addresses_remove_arguments; /* Controls -t behaviour */ extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */ +extern BOOL fake_reject; /* TRUE if fake reject is to be given */ extern int filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */ extern BOOL filter_running; /* TRUE while running a filter */ extern int filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */ @@ -268,6 +285,7 @@ extern uschar *filter_thisaddress; /* For address looping */ extern int finduser_retries; /* Retry count for getpwnam() */ extern uid_t fixed_never_users[]; /* Can't be overridden */ +extern uschar *found_extension; /* demime acl condition: file extension found */ extern uschar *freeze_tell; /* Message on (some) freezings */ extern uschar *fudged_queue_times; /* For use in test harness */ @@ -346,6 +364,7 @@ extern macro_item *macros; /* Configuration macros */ extern uschar *mailstore_basename; /* For mailstore deliveries */ +extern uschar *malware_name; /* Name of virus or malware ("W32/Klez-H") */ extern int max_username_length; /* For systems with broken getpwnam() */ extern int message_age; /* In seconds */ extern uschar *message_body; /* Start of message body for filter */ @@ -365,8 +384,25 @@ extern uschar *message_size_limit; /* As it says */ extern uschar message_subdir[]; /* Subdirectory for messages */ extern uschar *message_reference; /* Reference for error messages */ +extern uschar *mime_anomaly_level; +extern uschar *mime_anomaly_text; +extern uschar *mime_boundary; +extern uschar *mime_charset; +extern uschar *mime_content_description; +extern uschar *mime_content_disposition; +extern uschar *mime_content_id; +extern unsigned int mime_content_size; +extern uschar *mime_content_transfer_encoding; +extern uschar *mime_content_type; +extern uschar *mime_decoded_filename; +extern uschar *mime_filename; +extern int mime_is_multipart; +extern int mime_is_coverletter; +extern int mime_is_rfc822; +extern int mime_part_count; extern uid_t *never_users; /* List of uids never to be used */ +extern BOOL no_mbox_unspool; /* don't unspool exiscan files */ extern optionlist optionlist_auths[]; /* These option lists are made */ extern int optionlist_auths_size; /* global so that readconf can */ @@ -450,6 +486,7 @@ extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */ extern const pcre *regex_SIZE; /* For recognizing SIZE settings */ extern const pcre *regex_ismsgid; /* Compiled r.e. for message it */ +extern uschar *regex_match_string; /* regex that matched a line (regex ACL condition) */ extern int remote_delivery_count; /* Number of remote addresses */ extern int remote_max_parallel; /* Maximum parallel delivery */ extern uschar *remote_sort_domains; /* Remote domain sorting order */ @@ -540,7 +577,27 @@ extern BOOL smtp_use_pipelining; /* Global for passed connections */ extern BOOL smtp_use_size; /* Global for passed connections */ extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */ +extern uschar *spamd_address; /* address for the spamassassin daemon */ +extern uschar *spam_bar; /* the spam "bar" (textual representation of spam_score) */ +extern uschar *spam_report; /* the spamd report (multiline) */ +extern uschar *spam_score; /* the spam score (float) */ +extern uschar *spam_score_int; /* spam_score * 10 (int) */ +#ifdef SPF +extern uschar *spf_header_comment; /* spf header comment */ +extern uschar *spf_received; /* SPF-Received: header */ +extern uschar *spf_result; /* spf result in string form */ +extern uschar *spf_smtp_comment; /* spf comment to include in SMTP reply */ +#endif extern uschar *spool_directory; /* Name of spool directory */ +#ifdef SRS +extern uschar *srs_config; /* SRS config secret:max age:hash length:use timestamp:use hash */ +extern uschar *srs_db_address; /* SRS db address */ +extern uschar *srs_db_key; /* SRS db key */ +extern uschar *srs_orig_sender; /* SRS original sender */ +extern uschar *srs_orig_recipient; /* SRS original recipient */ +extern uschar *srs_recipient; /* SRS recipient */ +extern uschar *srs_status; /* SRS staus */ +#endif extern int string_datestamp_offset;/* After insertion by string_format */ extern BOOL strip_excess_angle_brackets; /* Surrounding route-addrs */ extern BOOL strip_trailing_dot; /* Remove dots at ends of domains */ diff -urN exim-4.41-orig/src/header.c exim-4.41/src/header.c --- exim-4.41-orig/src/header.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/header.c Mon Aug 2 13:27:37 2004 @@ -8,6 +8,78 @@ #include "exim.h" +/********************************************************************* +* Insert a predefined header at a specific position * +*********************************************************************/ + +/* This function adds a predefined header at a position in the list + determined by the header's "want_pos" member */ + +/* +Arguments: + hdr Pointer to a header structure + +Returns: nothing +*/ + +header_line *header_insert(header_line *hdr) +{ +header_line *insert_point_header = NULL; +header_line *hl = NULL; +header_line *orig_next = hdr->next; + +/* If either header_list or header_last are still unset, + we can't add headers. This is mainly a paranoia check */ +if (header_last == NULL) return; +if (header_list == NULL) return; + +switch (hdr->want_pos) { + case HEADER_POS_TOP: + /* add header at very top of list */ + hdr->next = header_list; + header_list = hdr; + break; + + case HEADER_POS_MIDDLE: + /* add header after all Received: and Resent-*: headers */ + insert_point_header = header_list; + hl = header_list; + while(hl != NULL) { + /* skip deleted headers */ + if (hl->type == '*') { + hl = hl->next; + continue; + }; + if ( (strcmpic(hl->text,"Received:") == 0) || + (strcmpic(hl->text,"Resent-") == 0) ) { + insert_point_header = hl; + }; + hl = hl->next; + }; + + /* check if we fell of the end of the list, + same as "add at bottom" */ + if (insert_point_header == NULL) + goto HEADER_ADD_BOTTOM; + else { + hdr->next = insert_point_header->next; + insert_point_header->next = hdr; + }; + + break; + + case HEADER_POS_BOTTOM: + /* add header at the bottom of the list */ + HEADER_ADD_BOTTOM: + header_last->next = hdr; + header_last = hdr; + header_last->next = NULL; + break; +}; + +return orig_next; +} + /************************************************* * Add new header on end of chain * diff -urN exim-4.41-orig/src/local_scan.h exim-4.41/src/local_scan.h --- exim-4.41-orig/src/local_scan.h Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/local_scan.h Mon Aug 2 12:54:24 2004 @@ -16,7 +16,6 @@ #include "mytypes.h" #include "store.h" - /* The function and its return codes. */ extern int local_scan(int, uschar **); @@ -89,8 +88,14 @@ int type; int slen; uschar *text; + int want_pos; } header_line; +/* possible values for header_line.wanted_pos */ +#define HEADER_POS_BOTTOM 0 +#define HEADER_POS_MIDDLE 1 +#define HEADER_POS_TOP 2 + /* Entries in lists options are in this form. */ typedef struct { @@ -107,6 +112,9 @@ uschar *address; /* the recipient address */ int pno; /* parent number for "one_time" alias, or -1 */ uschar *errors_to; /* the errors_to address or NULL */ +#ifdef BRIGHTMAIL + uschar *bmi_optin; +#endif } recipient_item; diff -urN exim-4.41-orig/src/lookups/cdb.h exim-4.41/src/lookups/cdb.h --- exim-4.41-orig/src/lookups/cdb.h Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/lookups/cdb.h Thu Jul 22 16:31:13 2004 @@ -3,7 +3,7 @@ *************************************************/ /* - * $Id: cdb.h,v 1.2.2.1 1998/05/29 16:21:36 cvs Exp $ + * $Id: cdb.h,v 1.1.1.1 2004/06/24 07:46:30 tkistner Exp $ * * Exim - CDB database lookup module * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -urN exim-4.41-orig/src/macros.h exim-4.41/src/macros.h --- exim-4.41-orig/src/macros.h Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/macros.h Thu Jul 22 16:31:13 2004 @@ -129,12 +129,12 @@ into big_buffer_size and in some circumstances increased. It should be at least as long as the maximum path length. */ -#if defined PATH_MAX && PATH_MAX > 1024 +#if defined PATH_MAX && PATH_MAX > 16384 #define BIG_BUFFER_SIZE PATH_MAX -#elif defined MAXPATHLEN && MAXPATHLEN > 1024 +#elif defined MAXPATHLEN && MAXPATHLEN > 16384 #define BIG_BUFFER_SIZE MAXPATHLEN #else -#define BIG_BUFFER_SIZE 1024 +#define BIG_BUFFER_SIZE 16384 #endif /* This limits the length of data returned by local_scan(). Because it is @@ -711,7 +711,7 @@ enum { ACL_WHERE_AUTH, ACL_WHERE_CONNECT, ACL_WHERE_DATA, ACL_WHERE_ETRN, ACL_WHERE_EXPN, ACL_WHERE_HELO, ACL_WHERE_MAIL, - ACL_WHERE_MAILAUTH, ACL_WHERE_RCPT, + ACL_WHERE_MAILAUTH, ACL_WHERE_MIME, ACL_WHERE_RCPT, ACL_WHERE_STARTTLS, ACL_WHERE_VRFY, ACL_WHERE_NOTSMTP }; /* Situations for spool_write_header() */ diff -urN exim-4.41-orig/src/malware.c exim-4.41/src/malware.c --- exim-4.41-orig/src/malware.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/malware.c Wed Aug 18 16:29:45 2004 @@ -0,0 +1,1301 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling virus (malware) scanners. Called from acl.c. */ + +#include "exim.h" + +/* declaration of private routines */ +int mksd_scan_packed(int sock); +int mksd_scan_unpacked(int sock, int maxproc); + +/* SHUT_WR seems to be undefined on Unixware ? */ +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#define DRWEBD_SCAN_CMD 0x0001 +#define DRWEBD_RETURN_VIRUSES 0x0001 + +/* Routine to check whether a system is big- or litte-endian. + Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html + Needed for proper kavdaemon implementation. Sigh. */ +#define BIG_MY_ENDIAN 0 +#define LITTLE_MY_ENDIAN 1 +int test_byte_order(void); +int test_byte_order() { + short int word = 0x0001; + char *byte = (char *) &word; + return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); +} + +uschar malware_name_buffer[256]; +int malware_ok = 0; + +int malware(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *av_scanner_work = av_scanner; + uschar *scanner_name; + uschar scanner_name_buffer[16]; + uschar *malware_regex; + uschar malware_regex_buffer[64]; + uschar malware_regex_default[] = ".+"; + unsigned long long mbox_size; + FILE *mbox_file; + int roffset; + const pcre *re; + const uschar *rerror; + + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: error while creating mbox spool file"); + return DEFER; + }; + /* none of our current scanners need the mbox + file as a stream, so we can close it right away */ + fclose(mbox_file); + + /* extract the malware regex to match against from the option list */ + if ((malware_regex = string_nextinlist(&list, &sep, + malware_regex_buffer, + sizeof(malware_regex_buffer))) != NULL) { + + /* parse 1st option */ + if ( (strcmpic(malware_regex,US"false") == 0) || + (Ustrcmp(malware_regex,"0") == 0) ) { + /* explicitly no matching */ + return FAIL; + }; + + /* special cases (match anything except empty) */ + if ( (strcmpic(malware_regex,US"true") == 0) || + (Ustrcmp(malware_regex,"*") == 0) || + (Ustrcmp(malware_regex,"1") == 0) ) { + malware_regex = malware_regex_default; + }; + } + else { + /* empty means "don't match anything" */ + return FAIL; + }; + + /* compile the regex, see if it works */ + re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (re == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset); + return DEFER; + }; + + /* if av_scanner starts with a dollar, expand it first */ + if (*av_scanner == '$') { + av_scanner_work = expand_string(av_scanner); + if (av_scanner_work == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: av_scanner starts with $, but expansion failed: %s", expand_string_message); + return DEFER; + } + else { + debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); + /* disable result caching in this case */ + malware_name = NULL; + malware_ok = 0; + }; + } + + /* Do not scan twice. */ + if (malware_ok == 0) { + + /* find the scanner type from the av_scanner option */ + if ((scanner_name = string_nextinlist(&av_scanner_work, &sep, + scanner_name_buffer, + sizeof(scanner_name_buffer))) == NULL) { + /* no scanner given */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: av_scanner configuration variable is empty"); + return DEFER; + }; + + /* "drweb" scanner type ----------------------------------------------- */ + /* v0.1 - added support for tcp sockets */ + /* v0.0 - initial release -- support for unix sockets */ + if (strcmpic(scanner_name,US"drweb") == 0) { + uschar *drweb_options; + uschar drweb_options_buffer[1024]; + uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock"; + struct sockaddr_un server; + int sock, port, result, ovector[30]; + unsigned int fsize; + uschar tmpbuf[1024], *drweb_fbuf; + uschar scanrequest[1024]; + uschar drweb_match_string[128]; + int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, + drweb_vnum, drweb_slen, drweb_fin = 0x0000; + unsigned long bread; + uschar hostname[256]; + struct hostent *he; + struct in_addr in; + pcre *drweb_re; + + if ((drweb_options = string_nextinlist(&av_scanner_work, &sep, + drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) { + /* no options supplied, use default options */ + drweb_options = drweb_options_default; + }; + + if (*drweb_options != '/') { + + /* extract host and port part */ + if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: invalid socket '%s'", drweb_options); + return DEFER; + } + + /* Lookup the host */ + if((he = gethostbyname(CS hostname)) == 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: failed to lookup host '%s'", hostname); + return DEFER; + } + + in = *(struct in_addr *) he->h_addr_list[0]; + + /* Open the drwebd TCP socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to acquire socket (%s)", + strerror(errno)); + return DEFER; + } + + if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: connection to %s, port %u failed (%s)", + inet_ntoa(in), port, strerror(errno)); + return DEFER; + } + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES); + snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", + spool_directory, message_id, message_id); + + /* calc file size */ + drweb_fd = open(CS scanrequest, O_RDONLY); + if (drweb_fd == -1) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't open spool file %s: %s", + scanrequest, strerror(errno)); + return DEFER; + } + fsize = lseek(drweb_fd, 0, SEEK_END); + if (fsize == -1) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't seek spool file %s: %s", + scanrequest, strerror(errno)); + return DEFER; + } + drweb_slen = htonl(fsize); + lseek(drweb_fd, 0, SEEK_SET); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { + close(sock); + close(drweb_fd); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); + return DEFER; + } + + drweb_fbuf = (uschar *) malloc (fsize); + if (!drweb_fbuf) { + close(sock); + close(drweb_fd); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to allocate memory %u for file (%s)", + fsize, scanrequest); + return DEFER; + } + + result = read (drweb_fd, drweb_fbuf, fsize); + if (result == -1) { + close(sock); + close(drweb_fd); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't read spool file %s: %s", + scanrequest, strerror(errno)); + return DEFER; + } + + /* send file body to socket */ + if (send(sock, drweb_fbuf, fsize, 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options); + return DEFER; + } + close(drweb_fd); + free(drweb_fbuf); + } + else { + /* open the drwebd UNIX socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't open UNIX socket"); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, drweb_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno); + return DEFER; + } + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES); + snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + drweb_slen = htonl(Ustrlen(scanrequest)); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) || + (send(sock, scanrequest, Ustrlen(scanrequest), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); + return DEFER; + } + } + + /* wait for result */ + if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to read return code"); + return DEFER; + } + drweb_rc = ntohl(drweb_rc); + + if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to read the number of viruses"); + return DEFER; + } + drweb_vnum = ntohl(drweb_vnum); + + /* "virus(es) found" if virus number is > 0 */ + if (drweb_vnum) + { + int i; + uschar pre_malware_nb[256]; + + malware_name = malware_name_buffer; + + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + + /* read and concatenate virus names into one string */ + for (i=0;i= 2) { + pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255); + } + /* the first name we just copy to malware_name */ + if (i==0) + Ustrcpy(CS malware_name_buffer, CS pre_malware_nb); + else { + /* concatenate each new virus name to previous */ + int slen = Ustrlen(malware_name_buffer); + if (slen < (slen+Ustrlen(pre_malware_nb))) { + Ustrcat(malware_name_buffer, "/"); + Ustrcat(malware_name_buffer, pre_malware_nb); + } + } + } + } + else { + /* no virus found */ + malware_name = NULL; + }; + close(sock); + } + /* ----------------------------------------------------------------------- */ + else if (strcmpic(scanner_name,US"aveserver") == 0) { + uschar *kav_options; + uschar kav_options_buffer[1024]; + uschar kav_options_default[] = "/var/run/aveserver"; + uschar buf[32768]; + uschar *p; + struct sockaddr_un server; + int sock; + + if ((kav_options = string_nextinlist(&av_scanner_work, &sep, + kav_options_buffer, + sizeof(kav_options_buffer))) == NULL) { + /* no options supplied, use default options */ + kav_options = kav_options_default; + }; + + /* open the aveserver socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, kav_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to aveserver UNIX socket (%s). errno=%d", kav_options, errno); + return DEFER; + } + + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, 32768); + + if (buf[0] != '2') { + /* aveserver is having problems */ + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: aveserver is unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ); + return DEFER; + }; + + /* prepare our command */ + snprintf(CS buf, 32768, "SCAN mPQRSTub %s/scan/%s/%s.eml\r\n", spool_directory, message_id, message_id); + + /* and send it */ + if (send(sock, buf, Ustrlen(buf), 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options); + return DEFER; + } + + malware_name = NULL; + /* read response lines, find malware name and final response */ + while (recv_line(sock, buf, 32768) > 0) { + debug_printf("aveserver: %s\n", buf); + if (buf[0] == '2') break; + if (Ustrncmp(buf,"322",3) == 0) { + uschar *p = Ustrchr(&buf[4],' '); + *p = '\0'; + Ustrcpy(malware_name_buffer,&buf[4]); + malware_name = malware_name_buffer; + }; + } + + close(sock); + } + /* "kavdaemon" scanner type ------------------------------------------------ */ + else if (strcmpic(scanner_name,US"kavdaemon") == 0) { + uschar *kav_options; + uschar kav_options_buffer[1024]; + uschar kav_options_default[] = "/var/run/AvpCtl"; + struct sockaddr_un server; + int sock; + time_t t; + uschar tmpbuf[1024]; + uschar scanrequest[1024]; + uschar kav_match_string[128]; + int kav_rc; + unsigned long kav_reportlen, bread; + pcre *kav_re; + + if ((kav_options = string_nextinlist(&av_scanner_work, &sep, + kav_options_buffer, + sizeof(kav_options_buffer))) == NULL) { + /* no options supplied, use default options */ + kav_options = kav_options_default; + }; + + /* open the kavdaemon socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, kav_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno); + return DEFER; + } + + /* get current date and time, build scan request */ + time(&t); + strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s/scan/%%s", localtime(&t)); + snprintf(CS scanrequest, 1024,CS tmpbuf, spool_directory, message_id); + + /* send scan request */ + if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options); + return DEFER; + } + + /* wait for result */ + if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read 2 bytes from kavdaemon socket."); + return DEFER; + } + + /* get errorcode from one nibble */ + if (test_byte_order() == LITTLE_MY_ENDIAN) { + kav_rc = tmpbuf[0] & 0x0F; + } + else { + kav_rc = tmpbuf[1] & 0x0F; + }; + + /* improper kavdaemon configuration */ + if ( (kav_rc == 5) || (kav_rc == 6) ) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files."); + return DEFER; + }; + + if (kav_rc == 1) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: kavdaemon reported 'scanning not completed' (code 1)."); + return DEFER; + }; + + if (kav_rc == 7) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7)."); + return DEFER; + }; + + /* code 8 is not handled, since it is ambigous. It appears mostly on + bounces where part of a file has been cut off */ + + /* "virus found" return codes (2-4) */ + if ((kav_rc > 1) && (kav_rc < 5)) { + int report_flag = 0; + + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + malware_name = malware_name_buffer; + + if (test_byte_order() == LITTLE_MY_ENDIAN) { + report_flag = tmpbuf[1]; + } + else { + report_flag = tmpbuf[0]; + }; + + /* read the report, if available */ + if( report_flag == 1 ) { + /* read report size */ + if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: cannot read report size from kavdaemon"); + return DEFER; + }; + + /* it's possible that avp returns av_buffer[1] == 1 but the + reportsize is 0 (!?) */ + if (kav_reportlen > 0) { + /* set up match regex, depends on retcode */ + if( kav_rc == 3 ) + Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$"); + else + Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$"); + + kav_re = pcre_compile( CS kav_match_string, + PCRE_COPT, + (const char **)&rerror, + &roffset, + NULL ); + + /* read report, linewise */ + while (kav_reportlen > 0) { + int result = 0; + int ovector[30]; + + bread = 0; + while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { + kav_reportlen--; + if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; + bread++; + }; + bread++; + tmpbuf[bread] = '\0'; + + /* try matcher on the line, grab substring */ + result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); + if (result >= 2) { + pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255); + break; + }; + }; + }; + }; + } + else { + /* no virus found */ + malware_name = NULL; + }; + + close(sock); + } + /* ----------------------------------------------------------------------- */ + + + /* "cmdline" scanner type ------------------------------------------------ */ + else if (strcmpic(scanner_name,US"cmdline") == 0) { + uschar *cmdline_scanner; + uschar cmdline_scanner_buffer[1024]; + uschar *cmdline_trigger; + uschar cmdline_trigger_buffer[1024]; + const pcre *cmdline_trigger_re; + uschar *cmdline_regex; + uschar cmdline_regex_buffer[1024]; + const pcre *cmdline_regex_re; + uschar file_name[1024]; + uschar commandline[1024]; + void (*eximsigchld)(int); + void (*eximsigpipe)(int); + FILE *scanner_out = NULL; + FILE *scanner_record = NULL; + uschar linebuffer[32767]; + int trigger = 0; + int result; + int ovector[30]; + + /* find scanner command line */ + if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep, + cmdline_scanner_buffer, + sizeof(cmdline_scanner_buffer))) == NULL) { + /* no command line supplied */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: missing commandline specification for cmdline scanner type."); + return DEFER; + }; + + /* find scanner output trigger */ + if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, + cmdline_trigger_buffer, + sizeof(cmdline_trigger_buffer))) == NULL) { + /* no trigger regex supplied */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: missing trigger specification for cmdline scanner type."); + return DEFER; + }; + + /* precompile trigger regex */ + cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (cmdline_trigger_re == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger_re, rerror, roffset); + return DEFER; + }; + + /* find scanner name regex */ + if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep, + cmdline_regex_buffer, + sizeof(cmdline_regex_buffer))) == NULL) { + /* no name regex supplied */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: missing virus name regex specification for cmdline scanner type."); + return DEFER; + }; + + /* precompile name regex */ + cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (cmdline_regex_re == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex_re, rerror, roffset); + return DEFER; + }; + + /* prepare scanner call */ + snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id); + snprintf(CS commandline,1024, CS cmdline_scanner,file_name); + /* redirect STDERR too */ + Ustrcat(commandline," 2>&1"); + + /* store exims signal handlers */ + eximsigchld = signal(SIGCHLD,SIG_DFL); + eximsigpipe = signal(SIGPIPE,SIG_DFL); + + scanner_out = popen(CS commandline,"r"); + if (scanner_out == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno)); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return DEFER; + }; + + snprintf(CS file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id); + scanner_record = fopen(CS file_name,"w"); + + if (scanner_record == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno)); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return DEFER; + }; + + /* look for trigger while recording output */ + while(fgets(CS linebuffer,32767,scanner_out) != NULL) { + if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { + /* short write */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: short write on scanner output file (%s).", file_name); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return DEFER; + }; + /* try trigger match */ + if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) + trigger = 1; + }; + + fclose(scanner_record); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + + if (trigger) { + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + malware_name = malware_name_buffer; + + /* re-open the scanner output file, look for name match */ + scanner_record = fopen(CS file_name,"r"); + while(fgets(CS linebuffer,32767,scanner_record) != NULL) { + /* try match */ + result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30); + if (result >= 2) { + pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255); + }; + }; + fclose(scanner_record); + } + else { + /* no virus found */ + malware_name = NULL; + }; + } + /* ----------------------------------------------------------------------- */ + + + /* "sophie" scanner type ------------------------------------------------- */ + else if (strcmpic(scanner_name,US"sophie") == 0) { + uschar *sophie_options; + uschar sophie_options_buffer[1024]; + uschar sophie_options_default[] = "/var/run/sophie"; + int bread = 0; + struct sockaddr_un server; + int sock; + uschar file_name[1024]; + uschar av_buffer[1024]; + + if ((sophie_options = string_nextinlist(&av_scanner_work, &sep, + sophie_options_buffer, + sizeof(sophie_options_buffer))) == NULL) { + /* no options supplied, use default options */ + sophie_options = sophie_options_default; + }; + + /* open the sophie socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, sophie_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno); + return DEFER; + } + + /* pass the scan directory to sophie */ + snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id); + if (write(sock, file_name, Ustrlen(file_name)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options); + return DEFER; + }; + + write(sock, "\n", 1); + + /* wait for result */ + memset(av_buffer, 0, sizeof(av_buffer)); + if ((!(bread = read(sock, av_buffer, sizeof(av_buffer))) > 0)) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options); + return DEFER; + }; + + close(sock); + + /* infected ? */ + if (av_buffer[0] == '1') { + if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; + Ustrcpy(malware_name_buffer,&av_buffer[2]); + malware_name = malware_name_buffer; + } + else if (!strncmp(CS av_buffer, "-1", 2)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: malware acl condition: sophie reported error"); + return DEFER; + } + else { + /* all ok, no virus */ + malware_name = NULL; + }; + } + /* ----------------------------------------------------------------------- */ + + + /* "clamd" scanner type ------------------------------------------------- */ + /* This code was contributed by David Saez */ + else if (strcmpic(scanner_name,US"clamd") == 0) { + uschar *clamd_options; + uschar clamd_options_buffer[1024]; + uschar clamd_options_default[] = "/tmp/clamd"; + uschar *p,*vname; + struct sockaddr_un server; + int sock,port,bread=0; + uschar file_name[1024]; + uschar av_buffer[1024]; + uschar hostname[256]; + struct hostent *he; + struct in_addr in; + + if ((clamd_options = string_nextinlist(&av_scanner_work, &sep, + clamd_options_buffer, + sizeof(clamd_options_buffer))) == NULL) { + /* no options supplied, use default options */ + clamd_options = clamd_options_default; + } + + /* socket does not start with '/' -> network socket */ + if (*clamd_options != '/') { + + /* extract host and port part */ + if( sscanf(CS clamd_options, "%s %u", hostname, &port) != 2 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: invalid socket '%s'", clamd_options); + return DEFER; + }; + + /* Lookup the host */ + if((he = gethostbyname(CS hostname)) == 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: failed to lookup host '%s'", hostname); + return DEFER; + } + + in = *(struct in_addr *) he->h_addr_list[0]; + + /* Open the ClamAV Socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to acquire socket (%s)", + strerror(errno)); + return DEFER; + } + + if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: connection to %s, port %u failed (%s)", + inet_ntoa(in), port, strerror(errno)); + return DEFER; + } + } + else { + /* open the local socket */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to acquire socket (%s)", + strerror(errno)); + return DEFER; + } + + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, clamd_options); + + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)", + clamd_options, strerror(errno) ); + return DEFER; + } + } + + /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ + + snprintf(CS file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id); + + if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", + strerror(errno)); + return DEFER; + } + + /* + We're done sending, close socket for writing. + + One user reported that clamd 0.70 does not like this any more ... + + */ + + /* shutdown(sock, SHUT_WR); */ + + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = read(sock, av_buffer, sizeof(av_buffer)); + close(sock); + + if (!(bread > 0)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to read from socket (%s)", + strerror(errno)); + return DEFER; + } + + if (bread == sizeof(av_buffer)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: buffer too small"); + return DEFER; + } + + /* Check the result. ClamAV Returns + infected: -> ": FOUND" + not-infected: -> ": OK" + error: -> ": ERROR */ + + if (!(*av_buffer)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned null"); + return DEFER; + } + + /* colon in returned output? */ + if((p = Ustrrchr(av_buffer,':')) == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned malformed result: %s", + av_buffer); + return DEFER; + } + + /* strip filename strip CR at the end */ + ++p; + while (*p == ' ') ++p; + vname = p; + p = vname + Ustrlen(vname) - 1; + if( *p == '\n' ) *p = '\0'; + + if ((p = Ustrstr(vname, "FOUND"))!=NULL) { + *p=0; + for (--p;p>vname && *p<=32;p--) *p=0; + for (;*vname==32;vname++); + Ustrcpy(malware_name_buffer,vname); + malware_name = malware_name_buffer; + } + else { + if (Ustrstr(vname, "ERROR")!=NULL) { + /* ClamAV reports ERROR + Find line start */ + for (;*vname!='\n' && vname>av_buffer; vname--); + if (*vname=='\n') vname++; + + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned %s",vname); + return DEFER; + } + else { + /* Everything should be OK */ + malware_name = NULL; + } + } + } + /* ----------------------------------------------------------------------- */ + + + /* "mksd" scanner type --------------------------------------------------- */ + else if (strcmpic(scanner_name,US"mksd") == 0) { + uschar *mksd_options; + char *mksd_options_end; + uschar mksd_options_buffer[32]; + int mksd_maxproc = 1; /* default, if no option supplied */ + struct sockaddr_un server; + int sock; + int retval; + + if ((mksd_options = string_nextinlist(&av_scanner_work, &sep, + mksd_options_buffer, + sizeof(mksd_options_buffer))) != NULL) { + mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10); + if ((*mksd_options == '\0') || (*mksd_options_end != '\0') || + (mksd_maxproc < 1) || (mksd_maxproc > 32)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: mksd: invalid option '%s'", mksd_options); + return DEFER; + } + } + + /* open the mksd socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, "/var/run/mksd/socket"); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno); + return DEFER; + } + + malware_name = NULL; + + /* choose the appropriate scan routine */ + retval = demime_ok ? + mksd_scan_unpacked(sock, mksd_maxproc) : + mksd_scan_packed(sock); + + if (retval != OK) + return retval; + } + /* ----------------------------------------------------------------------- */ + + /* "unknown" scanner type ------------------------------------------------- */ + else { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware condition: unknown scanner type '%s'", scanner_name); + return DEFER; + }; + /* ----------------------------------------------------------------------- */ + + /* set "been here, done that" marker */ + malware_ok = 1; + }; + + /* match virus name against pattern (caseless ------->----------v) */ + if ( (malware_name != NULL) && + (regex_match_and_setup(re, malware_name, 0, -1)) ) { + return OK; + } + else { + return FAIL; + }; +} + + +/* simple wrapper for reading lines from sockets */ +int recv_line(int sock, uschar *buffer, int size) { + uschar *p = buffer; + + memset(buffer,0,size); + /* read until \n */ + while(recv(sock,p,1,0) > -1) { + if ((p-buffer) > (size-2)) break; + if (*p == '\n') break; + if (*p != '\r') p++; + }; + *p = '\0'; + + return (p-buffer); +} + + +/* ============= private routines for the "mksd" scanner type ============== */ + +#include + +int mksd_writev (int sock, struct iovec *iov, int iovcnt) +{ + int i; + + for (;;) { + do + i = writev (sock, iov, iovcnt); + while ((i < 0) && (errno == EINTR)); + if (i <= 0) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)"); + return -1; + } + + for (;;) + if (i >= iov->iov_len) { + if (--iovcnt == 0) + return 0; + i -= iov->iov_len; + iov++; + } else { + iov->iov_len -= i; + iov->iov_base = CS iov->iov_base + i; + break; + } + } +} + +int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) +{ + int offset = 0; + int i; + + do { + if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)"); + return -1; + } + + offset += i; + /* offset == av_buffer_size -> buffer full */ + if (offset == av_buffer_size) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: malformed reply received from mksd"); + return -1; + } + } while (av_buffer[offset-1] != '\n'); + + av_buffer[offset] = '\0'; + return offset; +} + +int mksd_parse_line (char *line) +{ + char *p; + + switch (*line) { + case 'O': + /* OK */ + return OK; + case 'E': + case 'A': + /* ERR */ + if ((p = strchr (line, '\n')) != NULL) + (*p) = '\0'; + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: mksd scanner failed: %s", line); + return DEFER; + default: + /* VIR */ + if ((p = strchr (line, '\n')) != NULL) { + (*p) = '\0'; + if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' ')) + if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) { + (*p) = '\0'; + Ustrcpy (malware_name_buffer, line+4); + malware_name = malware_name_buffer; + return OK; + } + } + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: malformed reply received from mksd: %s", line); + return DEFER; + } +} + +int mksd_scan_packed (int sock) +{ + struct iovec iov[7]; + char *cmd = "MSQ/scan/.eml\n"; + uschar av_buffer[1024]; + + iov[0].iov_base = cmd; + iov[0].iov_len = 3; + iov[1].iov_base = CS spool_directory; + iov[1].iov_len = Ustrlen (spool_directory); + iov[2].iov_base = cmd + 3; + iov[2].iov_len = 6; + iov[3].iov_base = iov[5].iov_base = CS message_id; + iov[3].iov_len = iov[5].iov_len = Ustrlen (message_id); + iov[4].iov_base = cmd + 3; + iov[4].iov_len = 1; + iov[6].iov_base = cmd + 9; + iov[6].iov_len = 5; + + if (mksd_writev (sock, iov, 7) < 0) + return DEFER; + + if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0) + return DEFER; + + close (sock); + + return mksd_parse_line (CS av_buffer); +} + +int mksd_scan_unpacked (int sock, int maxproc) +{ + struct iovec iov[5]; + char *cmd = "\nSQ/"; + DIR *unpdir; + struct dirent *entry; + int pending = 0; + uschar *line; + int i, offset; + uschar mbox_name[1024]; + uschar unpackdir[1024]; + uschar av_buffer[16384]; + + snprintf (CS mbox_name, sizeof (mbox_name), "%s.eml", CS message_id); + snprintf (CS unpackdir, sizeof (unpackdir), "%s/scan/%s", CS spool_directory, CS message_id); + + if ((unpdir = opendir (CS unpackdir)) == NULL) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to scan spool directory"); + return DEFER; + } + + iov[0].iov_base = cmd; + iov[0].iov_len = 3; + iov[1].iov_base = CS unpackdir; + iov[1].iov_len = Ustrlen (unpackdir); + iov[2].iov_base = cmd + 3; + iov[2].iov_len = 1; + iov[4].iov_base = cmd; + iov[4].iov_len = 1; + + /* main loop */ + while ((unpdir != NULL) || (pending > 0)) { + + /* write loop */ + while ((pending < maxproc) && (unpdir != NULL)) { + if ((entry = readdir (unpdir)) != NULL) { + if ((Ustrcmp (entry->d_name, ".") != 0) && + (Ustrcmp (entry->d_name, "..") != 0) && + (Ustrcmp (entry->d_name, mbox_name) != 0)) { + iov[3].iov_base = entry->d_name; + iov[3].iov_len = strlen (entry->d_name); + if (mksd_writev (sock, iov, 5) < 0) { + closedir (unpdir); + return DEFER; + } + iov[0].iov_base = cmd + 1; + iov[0].iov_len = 2; + pending++; + } + } else { + closedir (unpdir); + unpdir = NULL; + } + } + + /* read and parse */ + if (pending > 0) { + if ((offset = mksd_read_lines (sock, av_buffer, sizeof (av_buffer))) < 0) { + if (unpdir != NULL) + closedir (unpdir); + return DEFER; + } + line = av_buffer; + do { + if (((i = mksd_parse_line (CS line)) != OK) || (malware_name != NULL)) { + close (sock); + if (unpdir != NULL) + closedir (unpdir); + return i; + } + pending--; + if ((line = Ustrchr (line, '\n')) == NULL) { + close (sock); + if (unpdir != NULL) + closedir (unpdir); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unterminated line received from mksd"); + return DEFER; + } + } while (++line != (av_buffer + offset)); + offset = 0; + } + } + + close (sock); + return OK; +} + diff -urN exim-4.41-orig/src/mime.c exim-4.41/src/mime.c --- exim-4.41-orig/src/mime.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/mime.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,712 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2004 */ +/* License: GPL */ + +#include "exim.h" +#include "mime.h" +#include + +FILE *mime_stream = NULL; +uschar *mime_current_boundary = NULL; + + +/************************************************* +* decode quoted-printable chars * +*************************************************/ + +/* gets called when we hit a = + returns: new pointer position + result code in c: + -2 - decode error + -1 - soft line break, no char + 0-255 - char to write +*/ + +unsigned int mime_qp_hstr_i(uschar *cptr) { + unsigned int i, j = 0; + while (cptr && *cptr && isxdigit(*cptr)) { + i = *cptr++ - '0'; + if (9 < i) i -= 7; + j <<= 4; + j |= (i & 0x0f); + } + return(j); +} + +uschar *mime_decode_qp_char(uschar *qp_p,int *c) { + uschar hex[] = {0,0,0}; + int nan = 0; + uschar *initial_pos = qp_p; + + /* advance one char */ + qp_p++; + + REPEAT_FIRST: + if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { + /* tab or whitespace may follow + just ignore it, but remember + that this is not a valid hex + encoding any more */ + nan = 1; + qp_p++; + goto REPEAT_FIRST; + } + else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + /* this is a valid hex char, if nan is unset */ + if (nan) { + /* this is illegal */ + *c = -2; + return initial_pos; + } + else { + hex[0] = *qp_p; + qp_p++; + }; + } + else if (*qp_p == '\n') { + /* hit soft line break already, continue */ + *c = -1; + return qp_p; + } + else { + /* illegal char here */ + *c = -2; + return initial_pos; + }; + + if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + if (hex[0] > 0) { + hex[1] = *qp_p; + /* do hex conversion */ + *c = mime_qp_hstr_i(hex); + qp_p++; + return qp_p; + } + else { + /* huh ? */ + *c = -2; + return initial_pos; + }; + } + else { + /* illegal char */ + *c = -2; + return initial_pos; + }; +} + + +uschar *mime_parse_line(uschar *buffer, uschar *encoding, int *num_decoded) { + uschar *data = NULL; + + data = (uschar *)malloc(Ustrlen(buffer)+2); + + if (encoding == NULL) { + /* no encoding type at all */ + NO_DECODING: + memcpy(data, buffer, Ustrlen(buffer)); + data[(Ustrlen(buffer))] = 0; + *num_decoded = Ustrlen(data); + return data; + } + else if (Ustrcmp(encoding,"base64") == 0) { + uschar *p = buffer; + int offset = 0; + + /* ----- BASE64 ---------------------------------------------------- */ + /* NULL out '\r' and '\n' chars */ + while (Ustrrchr(p,'\r') != NULL) { + *(Ustrrchr(p,'\r')) = '\0'; + }; + while (Ustrrchr(p,'\n') != NULL) { + *(Ustrrchr(p,'\n')) = '\0'; + }; + + while (*(p+offset) != '\0') { + /* hit illegal char ? */ + if (mime_b64[*(p+offset)] == 128) { + offset++; + } + else { + *p = mime_b64[*(p+offset)]; + p++; + }; + }; + *p = 255; + + /* line is translated, start bit shifting */ + p = buffer; + *num_decoded = 0; + while(*p != 255) { + uschar tmp_c; + + /* byte 0 ---------------------- */ + if (*(p+1) == 255) { + break; + } + data[(*num_decoded)] = *p; + data[(*num_decoded)] <<= 2; + tmp_c = *(p+1); + tmp_c >>= 4; + data[(*num_decoded)] |= tmp_c; + (*num_decoded)++; + p++; + /* byte 1 ---------------------- */ + if (*(p+1) == 255) { + break; + } + data[(*num_decoded)] = *p; + data[(*num_decoded)] <<= 4; + tmp_c = *(p+1); + tmp_c >>= 2; + data[(*num_decoded)] |= tmp_c; + (*num_decoded)++; + p++; + /* byte 2 ---------------------- */ + if (*(p+1) == 255) { + break; + } + data[(*num_decoded)] = *p; + data[(*num_decoded)] <<= 6; + data[(*num_decoded)] |= *(p+1); + (*num_decoded)++; + p+=2; + + }; + return data; + /* ----------------------------------------------------------------- */ + } + else if (Ustrcmp(encoding,"quoted-printable") == 0) { + uschar *p = buffer; + + /* ----- QP -------------------------------------------------------- */ + *num_decoded = 0; + while (*p != 0) { + if (*p == '=') { + int decode_qp_result; + + p = mime_decode_qp_char(p,&decode_qp_result); + + if (decode_qp_result == -2) { + /* Error from decoder. p is unchanged. */ + data[(*num_decoded)] = '='; + (*num_decoded)++; + p++; + } + else if (decode_qp_result == -1) { + break; + } + else if (decode_qp_result >= 0) { + data[(*num_decoded)] = decode_qp_result; + (*num_decoded)++; + }; + } + else { + data[(*num_decoded)] = *p; + (*num_decoded)++; + p++; + }; + }; + return data; + /* ----------------------------------------------------------------- */ + } + /* unknown encoding type, just dump as-is */ + else goto NO_DECODING; +} + + +FILE *mime_get_decode_file(uschar *pname, uschar *fname) { + FILE *f; + uschar *filename; + + filename = (uschar *)malloc(2048); + + if ((pname != NULL) && (fname != NULL)) { + snprintf(CS filename, 2048, "%s/%s", pname, fname); + f = fopen(CS filename,"w+"); + } + else if (pname == NULL) { + f = fopen(CS fname,"w+"); + } + else if (fname == NULL) { + int file_nr = 0; + int result = 0; + + /* must find first free sequential filename */ + do { + struct stat mystat; + snprintf(CS filename,2048,"%s/%s-%05u", pname, message_id, file_nr); + file_nr++; + /* security break */ + if (file_nr >= 1024) + break; + result = stat(CS filename,&mystat); + } + while(result != -1); + f = fopen(CS filename,"w+"); + }; + + /* set expansion variable */ + mime_decoded_filename = filename; + + return f; +} + + +int mime_decode(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *option; + uschar option_buffer[1024]; + uschar decode_path[1024]; + FILE *decode_file = NULL; + uschar *buffer = NULL; + long f_pos = 0; + unsigned int size_counter = 0; + + if (mime_stream == NULL) + return FAIL; + + f_pos = ftell(mime_stream); + + /* build default decode path (will exist since MBOX must be spooled up) */ + snprintf(CS decode_path,1024,"%s/scan/%s",spool_directory,message_id); + + /* reserve a line buffer to work in */ + buffer = (uschar *)malloc(MIME_MAX_LINE_LENGTH+1); + if (buffer == NULL) { + log_write(0, LOG_PANIC, + "decode ACL condition: can't allocate %d bytes of memory.", MIME_MAX_LINE_LENGTH+1); + return DEFER; + }; + + /* try to find 1st option */ + if ((option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer))) != NULL) { + + /* parse 1st option */ + if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { + /* explicitly no decoding */ + return FAIL; + }; + + if (Ustrcmp(option,"default") == 0) { + /* explicit default path + file names */ + goto DEFAULT_PATH; + }; + + if (option[0] == '/') { + struct stat statbuf; + + memset(&statbuf,0,sizeof(statbuf)); + + /* assume either path or path+file name */ + if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) ) + /* is directory, use it as decode_path */ + decode_file = mime_get_decode_file(option, NULL); + else + /* does not exist or is a file, use as full file name */ + decode_file = mime_get_decode_file(NULL, option); + } + else + /* assume file name only, use default path */ + decode_file = mime_get_decode_file(decode_path, option); + } + else + /* no option? patch default path */ + DEFAULT_PATH: decode_file = mime_get_decode_file(decode_path, NULL); + + if (decode_file == NULL) + return DEFER; + + /* read data linewise and dump it to the file, + while looking for the current boundary */ + while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) { + uschar *decoded_line = NULL; + int decoded_line_length = 0; + + if (mime_current_boundary != NULL) { + /* boundary line must start with 2 dashes */ + if (Ustrncmp(buffer,"--",2) == 0) { + if (Ustrncmp((buffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) + break; + }; + }; + + decoded_line = mime_parse_line(buffer, mime_content_transfer_encoding, &decoded_line_length); + /* write line to decode file */ + if (fwrite(decoded_line, 1, decoded_line_length, decode_file) < decoded_line_length) { + /* error/short write */ + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + return DEFER; + }; + size_counter += decoded_line_length; + + if (size_counter > 1023) { + if ((mime_content_size + (size_counter / 1024)) < 65535) + mime_content_size += (size_counter / 1024); + else + mime_content_size = 65535; + size_counter = (size_counter % 1024); + }; + + free(decoded_line); + } + + fclose(decode_file); + + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + + /* round up remaining size bytes to one k */ + if (size_counter) { + mime_content_size++; + }; + + return OK; +} + +int mime_get_header(FILE *f, uschar *header) { + int c = EOF; + int done = 0; + int header_value_mode = 0; + int header_open_brackets = 0; + int num_copied = 0; + + while(!done) { + + c = fgetc(f); + if (c == EOF) break; + + /* always skip CRs */ + if (c == '\r') continue; + + if (c == '\n') { + if (num_copied > 0) { + /* look if next char is '\t' or ' ' */ + c = fgetc(f); + if (c == EOF) break; + if ( (c == '\t') || (c == ' ') ) continue; + ungetc(c,f); + }; + /* end of the header, terminate with ';' */ + c = ';'; + done = 1; + }; + + /* skip control characters */ + if (c < 32) continue; + + if (header_value_mode) { + /* --------- value mode ----------- */ + /* skip leading whitespace */ + if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) ) + continue; + + /* we have hit a non-whitespace char, start copying value data */ + header_value_mode = 2; + + /* skip quotes */ + if (c == '"') continue; + + /* leave value mode on ';' */ + if (c == ';') { + header_value_mode = 0; + }; + /* -------------------------------- */ + } + else { + /* -------- non-value mode -------- */ + /* skip whitespace + tabs */ + if ( (c == ' ') || (c == '\t') ) + continue; + if (c == '\\') { + /* quote next char. can be used + to escape brackets. */ + c = fgetc(f); + if (c == EOF) break; + } + else if (c == '(') { + header_open_brackets++; + continue; + } + else if ((c == ')') && header_open_brackets) { + header_open_brackets--; + continue; + } + else if ( (c == '=') && !header_open_brackets ) { + /* enter value mode */ + header_value_mode = 1; + }; + + /* skip chars while we are in a comment */ + if (header_open_brackets > 0) + continue; + /* -------------------------------- */ + }; + + /* copy the char to the buffer */ + header[num_copied] = (uschar)c; + /* raise counter */ + num_copied++; + + /* break if header buffer is full */ + if (num_copied > MIME_MAX_HEADER_SIZE-1) { + done = 1; + }; + }; + + if (header[num_copied-1] != ';') { + header[num_copied-1] = ';'; + }; + + /* 0-terminate */ + header[num_copied] = '\0'; + + /* return 0 for EOF or empty line */ + if ((c == EOF) || (num_copied == 1)) + return 0; + else + return 1; +} + + +int mime_acl_check(FILE *f, struct mime_boundary_context *context, uschar + **user_msgptr, uschar **log_msgptr) { + int rc = OK; + uschar *header = NULL; + struct mime_boundary_context nested_context; + + /* reserve a line buffer to work in */ + header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1); + if (header == NULL) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1); + return DEFER; + }; + + /* Not actually used at the moment, but will be vital to fixing + * some RFC 2046 nonconformance later... */ + nested_context.parent = context; + + /* loop through parts */ + while(1) { + + /* reset all per-part mime variables */ + mime_anomaly_level = NULL; + mime_anomaly_text = NULL; + mime_boundary = NULL; + mime_charset = NULL; + mime_decoded_filename = NULL; + mime_filename = NULL; + mime_content_description = NULL; + mime_content_disposition = NULL; + mime_content_id = NULL; + mime_content_transfer_encoding = NULL; + mime_content_type = NULL; + mime_is_multipart = 0; + mime_content_size = 0; + + /* + If boundary is null, we assume that *f is positioned on the start of headers (for example, + at the very beginning of a message. + If a boundary is given, we must first advance to it to reach the start of the next header + block. + */ + + /* NOTE -- there's an error here -- RFC2046 specifically says to + * check for outer boundaries. This code doesn't do that, and + * I haven't fixed this. + * + * (I have moved partway towards adding support, however, by adding + * a "parent" field to my new boundary-context structure.) + */ + if (context != NULL) { + while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) { + /* boundary line must start with 2 dashes */ + if (Ustrncmp(header,"--",2) == 0) { + if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0) { + /* found boundary */ + if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0) { + /* END boundary found */ + debug_printf("End boundary found %s\n", context->boundary); + return rc; + } + else { + debug_printf("Next part with boundary %s\n", context->boundary); + }; + /* can't use break here */ + goto DECODE_HEADERS; + } + }; + } + /* Hit EOF or read error. Ugh. */ + debug_printf("Hit EOF ...\n"); + return rc; + }; + + DECODE_HEADERS: + /* parse headers, set up expansion variables */ + while(mime_get_header(f,header)) { + int i; + /* loop through header list */ + for (i = 0; i < mime_header_list_size; i++) { + uschar *header_value = NULL; + int header_value_len = 0; + + /* found an interesting header? */ + if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) { + uschar *p = header + mime_header_list[i].namelen; + /* yes, grab the value (normalize to lower case) + and copy to its corresponding expansion variable */ + while(*p != ';') { + *p = tolower(*p); + p++; + }; + header_value_len = (p - (header + mime_header_list[i].namelen)); + header_value = (uschar *)malloc(header_value_len+1); + memset(header_value,0,header_value_len+1); + p = header + mime_header_list[i].namelen; + Ustrncpy(header_value, p, header_value_len); + debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value); + *((uschar **)(mime_header_list[i].value)) = header_value; + + /* make p point to the next character after the closing ';' */ + p += (header_value_len+1); + + /* grab all param=value tags on the remaining line, check if they are interesting */ + NEXT_PARAM_SEARCH: while (*p != 0) { + int j; + for (j = 0; j < mime_parameter_list_size; j++) { + uschar *param_value = NULL; + int param_value_len = 0; + + /* found an interesting parameter? */ + if (strncmpic(mime_parameter_list[j].name,p,mime_parameter_list[j].namelen) == 0) { + uschar *q = p + mime_parameter_list[j].namelen; + /* yes, grab the value and copy to its corresponding expansion variable */ + while(*q != ';') q++; + param_value_len = (q - (p + mime_parameter_list[j].namelen)); + param_value = (uschar *)malloc(param_value_len+1); + memset(param_value,0,param_value_len+1); + q = p + mime_parameter_list[j].namelen; + Ustrncpy(param_value, q, param_value_len); + param_value = rfc2047_decode(param_value, TRUE, NULL, 32, ¶m_value_len, &q); + debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mime_parameter_list[j].name, mime_header_list[i].name, param_value); + *((uschar **)(mime_parameter_list[j].value)) = param_value; + p += (mime_parameter_list[j].namelen + param_value_len + 1); + goto NEXT_PARAM_SEARCH; + }; + } + /* There is something, but not one of our interesting parameters. + Advance to the next semicolon */ + while(*p != ';') p++; + p++; + }; + }; + }; + }; + + /* set additional flag variables (easier access) */ + if ( (mime_content_type != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) + mime_is_multipart = 1; + + /* Make a copy of the boundary pointer. + Required since mime_boundary is global + and can be overwritten further down in recursion */ + nested_context.boundary = mime_boundary; + + /* raise global counter */ + mime_part_count++; + + /* copy current file handle to global variable */ + mime_stream = f; + mime_current_boundary = context ? context->boundary : 0; + + /* Note the context */ + mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT); + + /* call ACL handling function */ + rc = acl_check(ACL_WHERE_MIME, NULL, acl_smtp_mime, user_msgptr, log_msgptr); + + mime_stream = NULL; + mime_current_boundary = NULL; + + if (rc != OK) break; + + /* If we have a multipart entity and a boundary, go recursive */ + if ( (mime_content_type != NULL) && + (nested_context.boundary != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) { + debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary); + + if (context && context->context == MBC_ATTACHMENT) + nested_context.context = MBC_ATTACHMENT; + else if (!Ustrcmp(mime_content_type,"multipart/alternative") + || !Ustrcmp(mime_content_type,"multipart/related")) + nested_context.context = MBC_COVERLETTER_ALL; + else + nested_context.context = MBC_COVERLETTER_ONESHOT; + + rc = mime_acl_check(f, &nested_context, user_msgptr, log_msgptr); + if (rc != OK) break; + } + else if ( (mime_content_type != NULL) && + (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) { + uschar *rfc822name = NULL; + uschar filename[2048]; + int file_nr = 0; + int result = 0; + + /* must find first free sequential filename */ + do { + struct stat mystat; + snprintf(CS filename,2048,"%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr); + file_nr++; + /* security break */ + if (file_nr >= 128) + goto NO_RFC822; + result = stat(CS filename,&mystat); + } + while(result != -1); + + rfc822name = filename; + + /* decode RFC822 attachment */ + mime_decoded_filename = NULL; + mime_stream = f; + mime_current_boundary = context ? context->boundary : NULL; + mime_decode(&rfc822name); + mime_stream = NULL; + mime_current_boundary = NULL; + if (mime_decoded_filename == NULL) { + /* decoding failed */ + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); + return DEFER; + }; + mime_decoded_filename = NULL; + }; + + NO_RFC822: + /* If the boundary of this instance is NULL, we are finished here */ + if (context == NULL) break; + + if (context->context == MBC_COVERLETTER_ONESHOT) + context->context = MBC_ATTACHMENT; + + }; + + return rc; +} + + diff -urN exim-4.41-orig/src/mime.h exim-4.41/src/mime.h --- exim-4.41-orig/src/mime.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/mime.h Thu Jul 22 16:31:13 2004 @@ -0,0 +1,77 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2004 */ +/* License: GPL */ + + +#define MIME_MAX_HEADER_SIZE 8192 +#define MIME_MAX_LINE_LENGTH 32768 + +#define MBC_ATTACHMENT 0 +#define MBC_COVERLETTER_ONESHOT 1 +#define MBC_COVERLETTER_ALL 2 + +struct mime_boundary_context +{ + struct mime_boundary_context *parent; + unsigned char *boundary; + int context; +}; + +typedef struct mime_header { + uschar *name; + int namelen; + void *value; +} mime_header; + +static mime_header mime_header_list[] = { + { US"content-type:", 13, &mime_content_type }, + { US"content-disposition:", 20, &mime_content_disposition }, + { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding }, + { US"content-id:", 11, &mime_content_id }, + { US"content-description:", 20 , &mime_content_description } +}; + +static int mime_header_list_size = sizeof(mime_header_list)/sizeof(mime_header); + + + +typedef struct mime_parameter { + uschar *name; + int namelen; + void *value; +} mime_parameter; + +static mime_parameter mime_parameter_list[] = { + { US"name=", 5, &mime_filename }, + { US"filename=", 9, &mime_filename }, + { US"charset=", 8, &mime_charset }, + { US"boundary=", 9, &mime_boundary } +}; + +static int mime_parameter_list_size = sizeof(mime_parameter_list)/sizeof(mime_parameter); + +/* BASE64 decoder matrix */ +static unsigned char mime_b64[256]={ +/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, +/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128, +/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128, +/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +/* 112 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128, +/* 128 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 144 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 160 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 176 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 192 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 208 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 224 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 240 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 +}; diff -urN exim-4.41-orig/src/readconf.c exim-4.41/src/readconf.c --- exim-4.41-orig/src/readconf.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/readconf.c Thu Jul 22 16:31:13 2004 @@ -141,6 +141,7 @@ { "acl_smtp_helo", opt_stringptr, &acl_smtp_helo }, { "acl_smtp_mail", opt_stringptr, &acl_smtp_mail }, { "acl_smtp_mailauth", opt_stringptr, &acl_smtp_mailauth }, + { "acl_smtp_mime", opt_stringptr, &acl_smtp_mime }, { "acl_smtp_rcpt", opt_stringptr, &acl_smtp_rcpt }, #ifdef SUPPORT_TLS { "acl_smtp_starttls", opt_stringptr, &acl_smtp_starttls }, @@ -152,7 +153,11 @@ { "allow_utf8_domains", opt_bool, &allow_utf8_domains }, { "auth_advertise_hosts", opt_stringptr, &auth_advertise_hosts }, { "auto_thaw", opt_time, &auto_thaw }, + { "av_scanner", opt_stringptr, &av_scanner }, { "bi_command", opt_stringptr, &bi_command }, +#ifdef BRIGHTMAIL + { "bmi_config_file", opt_stringptr, &bmi_config_file }, +#endif { "bounce_message_file", opt_stringptr, &bounce_message_file }, { "bounce_message_text", opt_stringptr, &bounce_message_text }, { "bounce_return_body", opt_bool, &bounce_return_body }, @@ -312,8 +317,12 @@ { "smtp_receive_timeout", opt_time, &smtp_receive_timeout }, { "smtp_reserve_hosts", opt_stringptr, &smtp_reserve_hosts }, { "smtp_return_error_details",opt_bool, &smtp_return_error_details }, + { "spamd_address", opt_stringptr, &spamd_address }, { "split_spool_directory", opt_bool, &split_spool_directory }, { "spool_directory", opt_stringptr, &spool_directory }, +#ifdef SRS + { "srs_config", opt_stringptr, &srs_config }, +#endif { "strip_excess_angle_brackets", opt_bool, &strip_excess_angle_brackets }, { "strip_trailing_dot", opt_bool, &strip_trailing_dot }, { "syslog_duplication", opt_bool, &syslog_duplication }, diff -urN exim-4.41-orig/src/receive.c exim-4.41/src/receive.c --- exim-4.41-orig/src/receive.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/receive.c Mon Aug 2 13:26:13 2004 @@ -10,7 +10,9 @@ #include "exim.h" - +#ifdef BRIGHTMAIL +#include "bmi_spam.h" +#endif /************************************************* * Local static variables * @@ -436,6 +438,13 @@ recipients_list[recipients_count].address = recipient; recipients_list[recipients_count].pno = pno; + +#ifdef BRIGHTMAIL +recipients_list[recipients_count].bmi_optin = bmi_current_optin; +/* reset optin string pointer for next recipient */ +bmi_current_optin = NULL; +#endif + recipients_list[recipients_count++].errors_to = NULL; } @@ -811,16 +820,15 @@ static void add_acl_headers(uschar *acl_name) { + if (acl_warn_headers != NULL) { DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name); - header_last->next = acl_warn_headers; - acl_warn_headers = NULL; - while (header_last->next != NULL) - { - header_last = header_last->next; - DEBUG(D_receive|D_acl) debug_printf(" %s", header_last->text); - } + + do { + DEBUG(D_receive|D_acl) debug_printf(" %s", acl_warn_headers->text); + } while( (acl_warn_headers = header_insert(acl_warn_headers)) != NULL); + DEBUG(D_receive|D_acl) debug_printf("\n"); } } @@ -2492,11 +2500,6 @@ } -/* Data file successfully written. If an ACL from any RCPT commands set up any -warning headers to add, do so now, before running the DATA ACL. */ - -add_acl_headers(US"MAIL or RCPT"); - /* Generate text for the Received: header by expanding the configured string, and adding a timestamp. By leaving this operation till now, we ensure that the timestamp is the time that message reception was completed. However, this is @@ -2549,6 +2552,11 @@ debug_printf(">>Generated Received: header line\n%c %s", header_list->type, header_list->text); +/* Data file successfully written. If an ACL from any RCPT commands set up any +warning headers to add, do so now, before running the DATA ACL. */ + +add_acl_headers(US"MAIL or RCPT"); + /* If an ACL is specified for checking things at this stage of reception of a message, run it, unless all the recipients were removed by "discard" in earlier ACLs. That is the only case in which recipients_count can be zero at this @@ -2570,6 +2578,124 @@ if (smtp_input && !smtp_batched_input) { + if (acl_smtp_mime != NULL && recipients_count > 0) + { + FILE *mbox_file; + uschar rfc822_file_path[2048]; + unsigned long long mbox_size; + header_line *my_headerlist; + uschar *user_msg, *log_msg; + int mime_part_count_buffer = -1; + + memset(CS rfc822_file_path,0,2048); + + /* check if it is a MIME message */ + my_headerlist = header_list; + while (my_headerlist != NULL) { + /* skip deleted headers */ + if (my_headerlist->type == '*') { + my_headerlist = my_headerlist->next; + continue; + }; + if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) { + DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n"); + goto DO_MIME_ACL; + }; + my_headerlist = my_headerlist->next; + }; + + DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n"); + goto NO_MIME_ACL; + + DO_MIME_ACL: + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected."); + Uunlink(spool_name); + unspool_mbox(); + smtp_respond(451, TRUE, US"temporary local problem"); + message_id[0] = 0; /* Indicate no message accepted */ + smtp_reply = US""; /* Indicate reply already sent */ + goto TIDYUP; /* Skip to end of function */ + }; + + mime_is_rfc822 = 0; + + MIME_ACL_CHECK: + mime_part_count = -1; + rc = mime_acl_check(mbox_file, NULL, &user_msg, &log_msg); + fclose(mbox_file); + + if (Ustrlen(rfc822_file_path) > 0) { + mime_part_count = mime_part_count_buffer; + + if (unlink(CS rfc822_file_path) == -1) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't unlink RFC822 spool file, skipping."); + goto END_MIME_ACL; + }; + }; + + /* check if we must check any message/rfc822 attachments */ + if (rc == OK) { + uschar temp_path[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + snprintf(CS temp_path, 1024, "%s/scan/%s", spool_directory, message_id); + + tempdir = opendir(CS temp_path); + n = 0; + do { + entry = readdir(tempdir); + if (entry == NULL) break; + if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) { + snprintf(CS rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name); + debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path); + break; + }; + } while (1); + closedir(tempdir); + + if (entry != NULL) { + mbox_file = Ufopen(rfc822_file_path,"r"); + if (mbox_file == NULL) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't open RFC822 spool file, skipping."); + unlink(CS rfc822_file_path); + goto END_MIME_ACL; + }; + /* set RFC822 expansion variable */ + mime_is_rfc822 = 1; + mime_part_count_buffer = mime_part_count; + goto MIME_ACL_CHECK; + }; + }; + + END_MIME_ACL: + add_acl_headers(US"MIME"); + if (rc == DISCARD) + { + recipients_count = 0; + blackholed_by = US"MIME ACL"; + } + else if (rc != OK) + { + Uunlink(spool_name); + unspool_mbox(); + if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) + smtp_yield = FALSE; /* No more messsages after dropped connection */ + smtp_reply = US""; /* Indicate reply already sent */ + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ + }; + } + + NO_MIME_ACL: if (acl_smtp_data != NULL && recipients_count > 0) { uschar *user_msg, *log_msg; @@ -2583,6 +2709,7 @@ else if (rc != OK) { Uunlink(spool_name); + unspool_mbox(); if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0) smtp_yield = FALSE; /* No more messsages after dropped connection */ smtp_reply = US""; /* Indicate reply already sent */ @@ -2592,6 +2719,7 @@ } } + /* Handle non-SMTP and batch SMTP (i.e. non-interactive) messages. Note that we cannot take different actions for permanent and temporary rejections. */ @@ -2632,6 +2760,8 @@ enable_dollar_recipients = FALSE; } +unspool_mbox(); + /* The final check on the message is to run the scan_local() function. The version supplied with Exim always accepts, but this is a hook for sysadmins to supply their own checking code. The local_scan() function is run even when all @@ -2809,6 +2939,16 @@ timestamp = expand_string(US"${tod_full}"); memcpy(header_list->text + received_length + 2, timestamp, Ustrlen(timestamp)); + +#ifdef BRIGHTMAIL +if (bmi_run == 1) { + /* rewind data file */ + lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); + bmi_verdicts = bmi_process_message(header_list, data_fd); +}; +#endif + + /* Keep the data file open until we have written the header file, in order to hold onto the lock. In a -bh run, or if the message is to be blackholed, we don't write the header file, and we unlink the data file. If writing the header @@ -3051,6 +3191,7 @@ if this happens? */ TIDYUP: + process_info[process_info_len] = 0; /* Remove message id */ if (data_file != NULL) fclose(data_file); /* Frees the lock */ @@ -3077,12 +3218,31 @@ { if (smtp_reply == NULL) { - smtp_printf("250 OK id=%s\r\n", message_id); + if (fake_reject) + { + smtp_printf("550-FAKE_REJECT id=%s\r\n", message_id); + smtp_printf("550-Your message has been rejected but is being kept for evaluation.\r\n"); + smtp_printf("550 If it was a legit message, it may still be delivered to the target recipient(s).\r\n"); + } + else + smtp_printf("250 OK id=%s\r\n", message_id); + if (host_checking) fprintf(stdout, "\n**** SMTP testing: that is not a real message id!\n\n"); + } - else if (smtp_reply[0] != 0) smtp_printf("%.1024s\r\n", smtp_reply); + else if (smtp_reply[0] != 0) + { + if (fake_reject && (smtp_reply[0] == '2')) + { + smtp_printf("550-FAKE_REJECT id=%s\r\n", message_id); + smtp_printf("550-Your message has been rejected but is being kept for evaluation.\r\n"); + smtp_printf("550 If it was a legit message, it may still be delivered to the target recipient(s).\r\n"); + } + else + smtp_printf("%.1024s\r\n", smtp_reply); + }; } /* For batched SMTP, generate an error message on failure, and do diff -urN exim-4.41-orig/src/regex.c exim-4.41/src/regex.c --- exim-4.41-orig/src/regex.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/regex.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,246 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for matching regular expressions against headers and body. + Called from acl.c. */ + +#include "exim.h" +#include +#include + +/* Structure to hold a list of Regular expressions */ +typedef struct pcre_list { + pcre *re; + uschar *pcre_text; + struct pcre_list *next; +} pcre_list; + +uschar regex_match_string_buffer[1024]; + +extern FILE *mime_stream; +extern uschar *mime_current_boundary; + +int regex(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *regex_string; + uschar regex_string_buffer[1024]; + unsigned long long mbox_size; + FILE *mbox_file; + pcre *re; + pcre_list *re_list_head = NULL; + pcre_list *re_list_item; + const char *pcre_error; + int pcre_erroffset; + uschar *linebuffer; + long f_pos = 0; + + /* reset expansion variable */ + regex_match_string = NULL; + + if (mime_stream == NULL) { + /* We are in the DATA ACL */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "regex acl condition: error while creating mbox spool file"); + return DEFER; + }; + } + else { + f_pos = ftell(mime_stream); + mbox_file = mime_stream; + }; + + /* precompile our regexes */ + while ((regex_string = string_nextinlist(&list, &sep, + regex_string_buffer, + sizeof(regex_string_buffer))) != NULL) { + + /* parse option */ + if ( (strcmpic(regex_string,US"false") == 0) || + (Ustrcmp(regex_string,"0") == 0) ) { + /* explicitly no matching */ + continue; + }; + + /* compile our regular expression */ + re = pcre_compile( CS regex_string, + 0, + &pcre_error, + &pcre_erroffset, + NULL ); + + if (re == NULL) { + log_write(0, LOG_MAIN, + "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); + continue; + } + else { + re_list_item = store_get(sizeof(pcre_list)); + re_list_item->re = re; + re_list_item->pcre_text = string_copy(regex_string); + re_list_item->next = re_list_head; + re_list_head = re_list_item; + }; + }; + + /* no regexes -> nothing to do */ + if (re_list_head == NULL) { + return FAIL; + }; + + /* match each line against all regexes */ + linebuffer = store_get(32767); + while (fgets(CS linebuffer, 32767, mbox_file) != NULL) { + if ( (mime_stream != NULL) && (mime_current_boundary != NULL) ) { + /* check boundary */ + if (Ustrncmp(linebuffer,"--",2) == 0) { + if (Ustrncmp((linebuffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) + /* found boundary */ + break; + }; + }; + re_list_item = re_list_head; + do { + /* try matcher on the line */ + if (pcre_exec(re_list_item->re, NULL, CS linebuffer, + (int)Ustrlen(linebuffer), 0, 0, NULL, 0) >= 0) { + Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); + regex_match_string = regex_match_string_buffer; + if (mime_stream == NULL) + fclose(mbox_file); + else { + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + }; + return OK; + }; + re_list_item = re_list_item->next; + } while (re_list_item != NULL); + }; + + if (mime_stream == NULL) + fclose(mbox_file); + else { + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + }; + + /* no matches ... */ + return FAIL; +} + + +int mime_regex(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *regex_string; + uschar regex_string_buffer[1024]; + pcre *re; + pcre_list *re_list_head = NULL; + pcre_list *re_list_item; + const char *pcre_error; + int pcre_erroffset; + FILE *f; + uschar *mime_subject = NULL; + int mime_subject_len = 0; + + /* reset expansion variable */ + regex_match_string = NULL; + + /* precompile our regexes */ + while ((regex_string = string_nextinlist(&list, &sep, + regex_string_buffer, + sizeof(regex_string_buffer))) != NULL) { + + /* parse option */ + if ( (strcmpic(regex_string,US"false") == 0) || + (Ustrcmp(regex_string,"0") == 0) ) { + /* explicitly no matching */ + continue; + }; + + /* compile our regular expression */ + re = pcre_compile( CS regex_string, + 0, + &pcre_error, + &pcre_erroffset, + NULL ); + + if (re == NULL) { + log_write(0, LOG_MAIN, + "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); + continue; + } + else { + re_list_item = store_get(sizeof(pcre_list)); + re_list_item->re = re; + re_list_item->pcre_text = string_copy(regex_string); + re_list_item->next = re_list_head; + re_list_head = re_list_item; + }; + }; + + /* no regexes -> nothing to do */ + if (re_list_head == NULL) { + return FAIL; + }; + + /* check if the file is already decoded */ + if (mime_decoded_filename == NULL) { + uschar *empty = US""; + /* no, decode it first */ + mime_decode(&empty); + if (mime_decoded_filename == NULL) { + /* decoding failed */ + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - could not decode MIME part to file."); + return DEFER; + }; + }; + + + /* open file */ + f = fopen(CS mime_decoded_filename, "r"); + if (f == NULL) { + /* open failed */ + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - can't open '%s' for reading.", mime_decoded_filename); + return DEFER; + }; + + /* get 32k memory */ + mime_subject = (uschar *)store_get(32767); + + /* read max 32k chars from file */ + mime_subject_len = fread(mime_subject, 1, 32766, f); + + re_list_item = re_list_head; + do { + /* try matcher on the mmapped file */ + debug_printf("Matching '%s'\n", re_list_item->pcre_text); + if (pcre_exec(re_list_item->re, NULL, CS mime_subject, + mime_subject_len, 0, 0, NULL, 0) >= 0) { + Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); + regex_match_string = regex_match_string_buffer; + fclose(f); + return OK; + }; + re_list_item = re_list_item->next; + } while (re_list_item != NULL); + + fclose(f); + + /* no matches ... */ + return FAIL; +} + diff -urN exim-4.41-orig/src/route.c exim-4.41/src/route.c --- exim-4.41-orig/src/route.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/route.c Thu Jul 22 16:31:13 2004 @@ -10,6 +10,9 @@ #include "exim.h" +#ifdef BRIGHTMAIL +#include "bmi_spam.h" +#endif /* Generic options for routers, all of which live inside router_instance @@ -32,6 +35,16 @@ (void *)(offsetof(router_instance, address_data)) }, { "address_test", opt_bool|opt_public, (void *)(offsetof(router_instance, address_test)) }, +#ifdef BRIGHTMAIL + { "bmi_deliver_alternate", opt_bool | opt_public, + (void *)(offsetof(router_instance, bmi_deliver_alternate)) }, + { "bmi_deliver_default", opt_bool | opt_public, + (void *)(offsetof(router_instance, bmi_deliver_default)) }, + { "bmi_dont_deliver", opt_bool | opt_public, + (void *)(offsetof(router_instance, bmi_dont_deliver)) }, + { "bmi_rule", opt_stringptr|opt_public, + (void *)(offsetof(router_instance, bmi_rule)) }, +#endif { "cannot_route_message", opt_stringptr | opt_public, (void *)(offsetof(router_instance, cannot_route_message)) }, { "caseful_local_part", opt_bool | opt_public, @@ -980,6 +993,49 @@ } } +#ifdef BRIGHTMAIL + +/* check if a specific Brightmail AntiSpam rule fired on the message */ +if (r->bmi_rule != NULL) { + DEBUG(D_route) debug_printf("checking bmi_rule\n"); + if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0) { + /* none of the rules fired */ + DEBUG(D_route) + debug_printf("%s router skipped: none of bmi_rule rules fired\n", r->name); + return SKIP; + }; +}; + +/* check if message should not be delivered */ +if (r->bmi_dont_deliver) { + if (bmi_deliver == 1) { + DEBUG(D_route) + debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", r->name); + return SKIP; + }; +}; + +/* check if message should go to an alternate location */ +if (r->bmi_deliver_alternate) { + if ((bmi_deliver == 0) || (bmi_alt_location == NULL)) { + DEBUG(D_route) + debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", r->name); + return SKIP; + }; +}; + +/* check if message should go to default location */ +if (r->bmi_deliver_default) { + if ((bmi_deliver == 0) || (bmi_alt_location != NULL)) { + DEBUG(D_route) + debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", r->name); + return SKIP; + }; +}; + +#endif + + /* All the checks passed. */ return OK; diff -urN exim-4.41-orig/src/routers/redirect.c exim-4.41/src/routers/redirect.c --- exim-4.41-orig/src/routers/redirect.c Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/routers/redirect.c Thu Jul 22 16:31:13 2004 @@ -9,6 +9,7 @@ #include "../exim.h" #include "rf_functions.h" #include "redirect.h" +#include "../srs.h" @@ -93,6 +94,14 @@ (void *)offsetof(redirect_router_options_block, bit_options) }, { "skip_syntax_errors", opt_bool, (void *)offsetof(redirect_router_options_block, skip_syntax_errors) }, +#ifdef SRS + { "srs", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs) }, + { "srs_condition", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs) }, + { "srs_db", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs_db) }, +#endif { "syntax_errors_text", opt_stringptr, (void *)offsetof(redirect_router_options_block, syntax_errors_text) }, { "syntax_errors_to", opt_stringptr, @@ -125,6 +134,11 @@ NULL, /* qualify_domain */ NULL, /* owners */ NULL, /* owngroups */ +#ifdef SRS + NULL, /* srs */ + NULL, /* srs_condition */ + NULL, /* srs_db */ +#endif 022, /* modemask */ RDO_REWRITE, /* bit_options */ FALSE, /* check_ancestor */ @@ -325,6 +339,7 @@ } } + /* A user filter may, under some circumstances, set up an errors address. If so, we must take care to re-instate it when we copy in the propagated data so that it overrides any errors_to setting on the router. */ @@ -517,6 +532,39 @@ ugid.gid_set = TRUE; } + +#ifdef SRS +/* For reverse SRS, fill the srs_recipient expandsion variable, +on failure, return decline/fail as relevant */ + + if(ob->srs != NULL) + { + BOOL usesrs = TRUE; + + if(ob->srs_condition != NULL) + usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL); + + if(usesrs) + if(Ustrcmp(ob->srs, "reverse") == 0 || Ustrcmp(ob->srs, "reverseandforward") == 0) + { + uschar *res; + int n_srs; + + srs_orig_recipient = addr->address; + eximsrs_init(); + if(ob->srs_db) + eximsrs_db_set(TRUE, ob->srs_db); + if((n_srs = eximsrs_reverse(&res, addr->address)) != OK) + return n_srs; + srs_recipient = res; + eximsrs_done(); + DEBUG(D_any) + debug_printf("SRS: Recipient '%s' rewriteen to '%s'\n", srs_orig_recipient, srs_recipient); + } + } +#endif + + /* Call the function that interprets redirection data, either inline or from a file. This is a separate function so that the system filter can use it. It will run the function in a subprocess if necessary. If qualify_preserve_domain is @@ -740,6 +788,37 @@ (addr_prop.errors_address != NULL)? "\n" : ""); } + +#ifdef SRS +/* On successful redirection, check for SRS forwarding and adjust sender */ + + if(ob->srs != NULL) + { + BOOL usesrs = TRUE; + + if(ob->srs_condition != NULL) + usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL); + + if(usesrs) + if((Ustrcmp(ob->srs, "forward") == 0 || Ustrcmp(ob->srs, "reverseandforward") == 0) && !verify) + { + uschar *res; + int n_srs; + + srs_orig_sender = sender_address; + eximsrs_init(); + if(ob->srs_db) + eximsrs_db_set(FALSE, ob->srs_db); + if((n_srs = eximsrs_forward(&res, sender_address, deliver_domain)) != OK) + return n_srs; + sender_address = res; + DEBUG(D_any) + debug_printf("SRS: Sender '%s' rewritten to '%s'\n", srs_orig_sender, sender_address); + } + } +#endif + + /* Control gets here only when the address has been completely handled. Put the original address onto the succeed queue so that any retry items that get attached to it get processed. */ diff -urN exim-4.41-orig/src/routers/redirect.h exim-4.41/src/routers/redirect.h --- exim-4.41-orig/src/routers/redirect.h Thu Jul 22 10:46:52 2004 +++ exim-4.41/src/routers/redirect.h Thu Jul 22 16:31:13 2004 @@ -30,6 +30,12 @@ uid_t *owners; gid_t *owngroups; +#ifdef SRS + uschar *srs; + uschar *srs_condition; + uschar *srs_db; +#endif + int modemask; int bit_options; BOOL check_ancestor; @@ -41,7 +47,7 @@ BOOL hide_child_in_errmsg; BOOL one_time; BOOL qualify_preserve_domain; - BOOL skip_syntax_errors; + BOOL skip_syntax_errors; } redirect_router_options_block; /* Data for reading the private options. */ diff -urN exim-4.41-orig/src/smtp_in.c exim-4.41/src/smtp_in.c --- exim-4.41-orig/src/smtp_in.c Thu Jul 22 10:46:53 2004 +++ exim-4.41/src/smtp_in.c Thu Jul 22 16:31:13 2004 @@ -10,6 +10,9 @@ #include "exim.h" +#ifdef SPF +#include "spf.h" +#endif /* Initialize for TCP wrappers if so configured. It appears that the macro HAVE_IPV6 is used in some versions of the tcpd.h header, so we unset it before @@ -795,6 +798,8 @@ acl_warn_headers = NULL; queue_only_policy = FALSE; deliver_freeze = FALSE; /* Can be set by ACL */ +fake_reject = FALSE; /* Can be set by ACL */ +no_mbox_unspool = FALSE; /* Can be set by ACL */ submission_mode = FALSE; /* Can be set by ACL */ sender_address = NULL; raw_sender = NULL; /* After SMTP rewrite, before qualifying */ @@ -803,6 +808,18 @@ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); authenticated_sender = NULL; +#ifdef BRIGHTMAIL +bmi_run = 0; +bmi_verdicts = NULL; +#endif +#ifdef SPF +spf_header_comment = NULL; +spf_received = NULL; +spf_result = NULL; +spf_smtp_comment = NULL; +#endif + + body_linecount = body_zerocount = 0; for (i = 0; i < ACL_M_MAX; i++) acl_var[ACL_C_MAX + i] = NULL; @@ -1754,8 +1771,10 @@ BOOL drop = rc == FAIL_DROP; uschar *lognl; uschar *sender_info = US""; -uschar *what = (where == ACL_WHERE_DATA)? US"after DATA" : - string_sprintf("%s %s", acl_wherenames[where], smtp_data); +uschar *what = string_sprintf("%s %s", acl_wherenames[where], smtp_data); + +if (where == ACL_WHERE_DATA) what = US"after DATA"; +if (where == ACL_WHERE_MIME) what = US"during MIME ACL checks"; if (drop) rc = FAIL; @@ -1765,7 +1784,7 @@ this is what should be logged, so I've changed to logging the unrewritten address to retain backward compatibility. */ -if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA) +if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME) { sender_info = string_sprintf("F=<%s> ", (sender_address_unrewritten != NULL)? sender_address_unrewritten : sender_address); @@ -2324,6 +2343,11 @@ } } +#ifdef SPF + /* set up SPF context */ + spf_init(sender_helo_name, sender_host_address); +#endif + /* Apply an ACL check if one is defined */ if (acl_smtp_helo != NULL) diff -urN exim-4.41-orig/src/spam.c exim-4.41/src/spam.c --- exim-4.41-orig/src/spam.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/spam.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,338 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling spamassassin's spamd. Called from acl.c. */ + +#include "exim.h" +#include "spam.h" + +uschar spam_score_buffer[16]; +uschar spam_score_int_buffer[16]; +uschar spam_bar_buffer[128]; +uschar spam_report_buffer[32600]; +uschar prev_user_name[128] = ""; +int spam_ok = 0; +int spam_rc = 0; + +int spam(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *user_name; + uschar user_name_buffer[128]; + unsigned long long mbox_size; + FILE *mbox_file; + int spamd_sock; + uschar spamd_buffer[32600]; + int i, j, offset; + uschar spamd_version[8]; + uschar spamd_score_char; + double spamd_threshold, spamd_score; + int spamd_report_offset; + uschar *p,*q; + int override = 0; + struct sockaddr_un server; + + /* find the username from the option list */ + if ((user_name = string_nextinlist(&list, &sep, + user_name_buffer, + sizeof(user_name_buffer))) == NULL) { + /* no username given, this means no scanning should be done */ + return FAIL; + }; + + /* if username is "0" or "false", do not scan */ + if ( (Ustrcmp(user_name,"0") == 0) || + (strcmpic(user_name,US"false") == 0) ) { + return FAIL; + }; + + /* if there is an additional option, check if it is "true" */ + if (strcmpic(list,US"true") == 0) { + /* in that case, always return true later */ + override = 1; + }; + + /* if we scanned for this username last time, just return */ + if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) ) { + if (override) + return OK; + else + return spam_rc; + }; + + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error while creating mbox spool file"); + return DEFER; + }; + + /* socket does not start with '/' -> network socket */ + if (*spamd_address != '/') { + time_t now = time(NULL); + int num_servers = 0; + int current_server = 0; + int start_server = 0; + uschar *address = NULL; + uschar *spamd_address_list_ptr = spamd_address; + uschar address_buffer[256]; + spamd_address_container * spamd_address_vector[32]; + + /* Check how many spamd servers we have + and register their addresses */ + while ((address = string_nextinlist(&spamd_address_list_ptr, &sep, + address_buffer, + sizeof(address_buffer))) != NULL) { + + spamd_address_container *this_spamd = + (spamd_address_container *)store_get(sizeof(spamd_address_container)); + + /* grok spamd address and port */ + if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) { + log_write(0, LOG_MAIN, + "spam acl condition: warning - invalid spamd address: '%s'", address); + continue; + }; + + spamd_address_vector[num_servers] = this_spamd; + num_servers++; + if (num_servers > 31) + break; + }; + + /* check if we have at least one server */ + if (!num_servers) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: no useable spamd server addresses in spamd_address configuration option."); + fclose(mbox_file); + return DEFER; + }; + + current_server = start_server = (int)now % num_servers; + + while (1) { + + debug_printf("trying server %s, port %u\n", + spamd_address_vector[current_server]->tcp_addr, + spamd_address_vector[current_server]->tcp_port); + + /* contact a spamd */ + if ( (spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error creating IP socket for spamd"); + fclose(mbox_file); + return DEFER; + }; + + if (ip_connect( spamd_sock, + AF_INET, + spamd_address_vector[current_server]->tcp_addr, + spamd_address_vector[current_server]->tcp_port, + 5 ) > -1) { + /* connection OK */ + break; + }; + + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: warning - spamd connection to %s, port %u failed: %s", + spamd_address_vector[current_server]->tcp_addr, + spamd_address_vector[current_server]->tcp_port, + strerror(errno)); + current_server++; + if (current_server >= num_servers) + current_server = 0; + if (current_server == start_server) { + log_write(0, LOG_MAIN|LOG_PANIC, "spam acl condition: all spamd servers failed"); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + }; + }; + + } + else { + /* open the local socket */ + + if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: spamd: unable to acquire socket (%s)", + strerror(errno)); + fclose(mbox_file); + return DEFER; + } + + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, spamd_address); + + if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)", + spamd_address, strerror(errno) ); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + } + + } + + /* now we are connected to spamd on spamd_sock */ + snprintf(CS spamd_buffer, + sizeof(spamd_buffer), + "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %lld\r\n\r\n", + user_name, + mbox_size); + + /* send our request */ + if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { + close(spamd_sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: spamd send failed: %s", strerror(errno)); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + }; + + /* now send the file */ + do { + j = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file); + if (j > 0) { + i = send(spamd_sock,spamd_buffer,j,0); + if (i != j) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error/short send to spamd"); + close(spamd_sock); + fclose(mbox_file); + return DEFER; + }; + }; + } + while (j > 0); + + fclose(mbox_file); + + /* we're done sending, close socket for writing */ + shutdown(spamd_sock,SHUT_WR); + + /* read spamd response */ + memset(spamd_buffer, 0, sizeof(spamd_buffer)); + offset = 0; + while((i = ip_recv(spamd_sock, + spamd_buffer + offset, + sizeof(spamd_buffer) - offset - 1, + SPAMD_READ_TIMEOUT)) > 0 ) { + offset += i; + } + + /* error handling */ + if((i <= 0) && (errno != 0)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error reading from spamd socket: %s", strerror(errno)); + close(spamd_sock); + return DEFER; + } + + /* reading done */ + close(spamd_sock); + + /* dig in the spamd output and put the report in a multiline header, if requested */ + if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", + spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { + + /* try to fall back to pre-2.50 spamd output */ + if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", + spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: cannot parse spamd output"); + return DEFER; + }; + }; + + /* Create report. Since this is a multiline string, + we must hack it into shape first */ + p = &spamd_buffer[spamd_report_offset]; + q = spam_report_buffer; + while (*p != '\0') { + /* skip \r */ + if (*p == '\r') { + p++; + continue; + }; + *q = *p; + q++; + if (*p == '\n') { + *q = '\t'; + q++; + /* eat whitespace */ + while( (*p <= ' ') && (*p != '\0') ) { + p++; + }; + p--; + }; + p++; + }; + /* NULL-terminate */ + *q = '\0'; + q--; + /* cut off trailing leftovers */ + while (*q <= ' ') { + *q = '\0'; + q--; + }; + spam_report = spam_report_buffer; + + /* create spam bar */ + spamd_score_char = spamd_score > 0 ? '+' : '-'; + j = abs((int)(spamd_score)); + i = 0; + if( j != 0 ) { + while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) + spam_bar_buffer[i++] = spamd_score_char; + } + else{ + spam_bar_buffer[0] = '/'; + i = 1; + } + spam_bar_buffer[i] = '\0'; + spam_bar = spam_bar_buffer; + + /* create "float" spam score */ + snprintf(CS spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score); + spam_score = spam_score_buffer; + + /* create "int" spam score */ + j = (int)((spamd_score + 0.001)*10); + snprintf(CS spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j); + spam_score_int = spam_score_int_buffer; + + /* compare threshold against score */ + if (spamd_score >= spamd_threshold) { + /* spam as determined by user's threshold */ + spam_rc = OK; + } + else { + /* not spam */ + spam_rc = FAIL; + }; + + /* remember user name and "been here" for it */ + Ustrcpy(prev_user_name, user_name); + spam_ok = 1; + + if (override) { + /* always return OK, no matter what the score */ + return OK; + } + else { + return spam_rc; + }; +} diff -urN exim-4.41-orig/src/spam.h exim-4.41/src/spam.h --- exim-4.41-orig/src/spam.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/spam.h Thu Jul 22 16:31:13 2004 @@ -0,0 +1,30 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* spam defines */ + +/* timeout for reading from spamd */ +#define SPAMD_READ_TIMEOUT 3600 + +/* maximum length of the spam bar */ +#define MAX_SPAM_BAR_CHARS 50 + +/* SHUT_WR seems to be undefined on Unixware ? */ +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +typedef struct spamd_address_container { + uschar tcp_addr[24]; + unsigned int tcp_port; +} spamd_address_container; + + + diff -urN exim-4.41-orig/src/spf.c exim-4.41/src/spf.c --- exim-4.41-orig/src/spf.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/spf.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,131 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling spf checks via libspf-alt. Called from acl.c. */ + +#ifdef SPF + +#include "exim.h" +#include "spf.h" + +SPF_config_t spfcid = NULL; +SPF_dns_config_t spfdcid_resolv = NULL; +SPF_dns_config_t spfdcid = NULL; + + +/* spf_init sets up a context that can be re-used for several + messages on the same SMTP connection (that come from the + same host with the same HELO string) */ + +int spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr) { + uschar *p; + + /* paranoia */ + spfcid = NULL; + spfdcid_resolv = NULL; + spfdcid = NULL; + + spfcid = SPF_create_config(); + if ( spfcid == NULL ) { + debug_printf("spf: SPF_create_config() failed.\n"); + return 0; + } + + /* set up resolver */ + spfdcid_resolv = SPF_dns_create_config_resolv(NULL, 0); + spfdcid = SPF_dns_create_config_cache(spfdcid_resolv, 8, 0); + + if (spfdcid == NULL) { + debug_printf("spf: SPF_dns_create_config_cache() failed.\n"); + spfcid = NULL; + spfdcid_resolv = NULL; + return 0; + } + + if (SPF_set_ip_str(spfcid, spf_remote_addr)) { + debug_printf("spf: SPF_set_ip_str() failed.\n"); + spfcid = NULL; + spfdcid_resolv = NULL; + return 0; + } + + if (SPF_set_helo_dom(spfcid, spf_helo_domain)) { + debug_printf("spf: SPF_set_helo_dom() failed.\n"); + spfcid = NULL; + spfdcid_resolv = NULL; + return 0; + } + + return 1; +} + + +/* spf_process adds the envelope sender address to the existing + context (if any), retrieves the result, sets up expansion + strings and evaluates the condition outcome. */ + +int spf_process(uschar **listptr, uschar *spf_envelope_sender) { + int sep = 0; + uschar *list = *listptr; + uschar *spf_result_id; + uschar spf_result_id_buffer[128]; + SPF_output_t spf_output; + int rc = SPF_RESULT_ERROR; + + if (!(spfcid && spfdcid)) { + /* no global context, assume temp error and skip to evaluation */ + rc = SPF_RESULT_ERROR; + goto SPF_EVALUATE; + }; + + if (SPF_set_env_from(spfcid, spf_envelope_sender)) { + /* Invalid sender address. This should be a real rare occurence */ + rc = SPF_RESULT_ERROR; + goto SPF_EVALUATE; + } + + /* get SPF result */ + spf_output = SPF_result(spfcid, spfdcid); + + /* set up expansion items */ + spf_header_comment = spf_output.header_comment ? (uschar *)spf_output.header_comment : NULL; + spf_received = spf_output.received_spf ? (uschar *)spf_output.received_spf : NULL; + spf_result = (uschar *)SPF_strresult(spf_output.result); + spf_smtp_comment = spf_output.smtp_comment ? (uschar *)spf_output.smtp_comment : NULL; + + rc = spf_output.result; + + /* We got a result. Now see if we should return OK or FAIL for it */ + SPF_EVALUATE: + debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc); + while ((spf_result_id = string_nextinlist(&list, &sep, + spf_result_id_buffer, + sizeof(spf_result_id_buffer))) != NULL) { + int negate = 0; + int result = 0; + + /* Check for negation */ + if (spf_result_id[0] == '!') { + negate = 1; + spf_result_id++; + }; + + /* Check the result identifier */ + result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name); + if (!negate && result==0) return OK; + if (negate && result!=0) return OK; + }; + + /* no match */ + return FAIL; +} + +#endif + diff -urN exim-4.41-orig/src/spf.h exim-4.41/src/spf.h --- exim-4.41-orig/src/spf.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/spf.h Fri Jul 23 13:28:17 2004 @@ -0,0 +1,39 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +#ifdef SPF + +#include +#include +#include + +typedef struct spf_result_id { + uschar *name; + int value; +} spf_result_id; + +/* must be kept in numeric order */ +static spf_result_id spf_result_id_list[] = { + { US"pass", 0 }, + { US"fail", 1 }, + { US"softfail", 2 }, + { US"neutral", 3 }, + { US"err_perm", 4 }, + { US"err_temp", 5 }, + { US"none", 6 } +}; + +static int spf_result_id_list_size = sizeof(spf_result_id_list)/sizeof(spf_result_id); + +/* prototypes */ +int spf_init(uschar *,uschar *); +int spf_process(uschar **, uschar *); + +#endif diff -urN exim-4.41-orig/src/spool_in.c exim-4.41/src/spool_in.c --- exim-4.41-orig/src/spool_in.c Thu Jul 22 10:46:53 2004 +++ exim-4.41/src/spool_in.c Mon Aug 2 13:36:48 2004 @@ -244,6 +244,7 @@ body_zerocount = 0; deliver_firsttime = FALSE; deliver_freeze = FALSE; +fake_reject = FALSE; deliver_frozen_at = 0; deliver_manual_thaw = FALSE; /* dont_deliver must NOT be reset */ @@ -253,6 +254,7 @@ interface_port = 0; local_error_message = FALSE; local_scan_data = NULL; +spam_score_int = NULL; message_linecount = 0; received_protocol = NULL; received_count = 0; @@ -269,6 +271,11 @@ sender_set_untrusted = FALSE; tree_nonrecipients = NULL; +#ifdef BRIGHTMAIL +bmi_run = 0; +bmi_verdicts = NULL; +#endif + #ifdef SUPPORT_TLS tls_certificate_verified = FALSE; tls_cipher = NULL; @@ -367,6 +374,12 @@ local_error_message = TRUE; else if (Ustrncmp(big_buffer, "-local_scan ", 12) == 0) local_scan_data = string_copy(big_buffer + 12); + else if (Ustrncmp(big_buffer, "-spam_score_int ", 16) == 0) + spam_score_int = string_copy(big_buffer + 16); +#ifdef BRIGHTMAIL + else if (Ustrncmp(big_buffer, "-bmi_verdicts ", 14) == 0) + bmi_verdicts = string_copy(big_buffer + 14); +#endif else if (Ustrcmp(big_buffer, "-host_lookup_failed") == 0) host_lookup_failed = TRUE; else if (Ustrncmp(big_buffer, "-body_linecount", 15) == 0) diff -urN exim-4.41-orig/src/spool_mbox.c exim-4.41/src/spool_mbox.c --- exim-4.41-orig/src/spool_mbox.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/spool_mbox.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,196 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for setting up a MBOX style spool file inside a /scan/ +sub directory of exim's spool directory. */ + +#include "exim.h" + +/* externals, we must reset them on unspooling */ +extern int demime_ok; +extern int malware_ok; +extern int spam_ok; +extern struct file_extension *file_extensions; + +int spool_mbox_ok = 0; +uschar spooled_message_id[17]; + +/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size */ + +FILE *spool_mbox(unsigned long long *mbox_file_size) { + uschar mbox_path[1024]; + uschar message_subdir[2]; + uschar data_buffer[65535]; + FILE *mbox_file; + FILE *data_file = NULL; + header_line *my_headerlist; + struct stat statbuf; + int i,j; + + /* + uschar *received; + uschar *timestamp; + */ + + if (!spool_mbox_ok) { + /* create scan directory, if not present */ + if (!directory_make(spool_directory, US "scan", 0750, FALSE)) { + debug_printf("unable to create directory: %s/scan\n", spool_directory); + return NULL; + }; + + /* create temp directory inside scan dir */ + snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, message_id); + if (!directory_make(NULL, mbox_path, 0750, FALSE)) { + debug_printf("unable to create directory: %s/scan/%s\n", spool_directory, message_id); + return NULL; + }; + + /* open [message_id].eml file for writing */ + snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + mbox_file = Ufopen(mbox_path,"w"); + + if (mbox_file == NULL) { + debug_printf("unable to open file for writing: %s\n", mbox_path); + return NULL; + }; + + /* Generate a preliminary Received: header and put it in the file. + We need to do this so SA can do DNS list checks */ + + /* removed for 4.34 + + timestamp = expand_string(US"${tod_full}"); + received = expand_string(received_header_text); + if (received != NULL) { + uschar *my_received; + if (received[0] == 0) { + my_received = string_sprintf("Received: ; %s\n", timestamp); + } + else { + my_received = string_sprintf("%s; %s\n", received, timestamp); + } + i = fwrite(my_received, 1, Ustrlen(my_received), mbox_file); + if (i != Ustrlen(my_received)) { + debug_printf("error/short write on writing in: %s", mbox_path); + fclose(mbox_file); + return NULL; + }; + }; + + */ + + /* write all header lines to mbox file */ + my_headerlist = header_list; + while (my_headerlist != NULL) { + + /* skip deleted headers */ + if (my_headerlist->type == '*') { + my_headerlist = my_headerlist->next; + continue; + }; + + i = fwrite(my_headerlist->text, 1, my_headerlist->slen, mbox_file); + if (i != my_headerlist->slen) { + debug_printf("error/short write on writing in: %s", mbox_path); + fclose(mbox_file); + return NULL; + }; + + my_headerlist = my_headerlist->next; + }; + + /* copy body file */ + message_subdir[1] = '\0'; + for (i = 0; i < 2; i++) { + message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0; + sprintf(CS mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id); + data_file = Ufopen(mbox_path,"r"); + if (data_file != NULL) + break; + }; + + fread(data_buffer, 1, 18, data_file); + + do { + j = fread(data_buffer, 1, sizeof(data_buffer), data_file); + if (j > 0) { + i = fwrite(data_buffer, 1, j, mbox_file); + if (i != j) { + debug_printf("error/short write on writing in: %s", mbox_path); + fclose(mbox_file); + fclose(data_file); + return NULL; + }; + }; + } while (j > 0); + + fclose(data_file); + fclose(mbox_file); + Ustrcpy(spooled_message_id, message_id); + spool_mbox_ok = 1; + }; + + snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + + /* get the size of the mbox message */ + stat(CS mbox_path, &statbuf); + *mbox_file_size = statbuf.st_size; + + /* open [message_id].eml file for reading */ + mbox_file = Ufopen(mbox_path,"r"); + + return mbox_file; +} + +/* remove mbox spool file, demimed files and temp directory */ +void unspool_mbox(void) { + + /* reset all exiscan state variables */ + demime_ok = 0; + demime_errorlevel = 0; + demime_reason = NULL; + file_extensions = NULL; + spam_ok = 0; + malware_ok = 0; + + if (spool_mbox_ok) { + + spool_mbox_ok = 0; + + if (!no_mbox_unspool) { + uschar mbox_path[1024]; + uschar file_path[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, spooled_message_id); + + tempdir = opendir(CS mbox_path); + /* loop thru dir & delete entries */ + n = 0; + do { + entry = readdir(tempdir); + if (entry == NULL) break; + snprintf(CS file_path, 1024,"%s/scan/%s/%s", spool_directory, spooled_message_id, entry->d_name); + if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) ) { + debug_printf("unspool_mbox(): unlinking '%s'\n", file_path); + n = unlink(CS file_path); + }; + } while (n > -1); + + closedir(tempdir); + + /* remove directory */ + n = rmdir(CS mbox_path); + }; + }; +} diff -urN exim-4.41-orig/src/spool_out.c exim-4.41/src/spool_out.c --- exim-4.41-orig/src/spool_out.c Thu Jul 22 10:46:53 2004 +++ exim-4.41/src/spool_out.c Thu Jul 22 16:31:13 2004 @@ -214,9 +214,14 @@ if (sender_local) fprintf(f, "-local\n"); if (local_error_message) fprintf(f, "-localerror\n"); if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data); +if (spam_score_int != NULL) fprintf(f,"-spam_score_int %s\n", spam_score_int); if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n"); if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n"); +#ifdef BRIGHTMAIL +if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts); +#endif + #ifdef SUPPORT_TLS if (tls_certificate_verified) fprintf(f, "-tls_certificate_verified\n"); if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher); diff -urN exim-4.41-orig/src/srs.c exim-4.41/src/srs.c --- exim-4.41-orig/src/srs.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/srs.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,209 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is an extension to Exim and is not part of the standard + Exim distribution */ + +/* (C)2004 Miles Wilton */ + +/* License: GPL */ + + +#ifdef SRS + +#include + +#include "exim.h" +#include "srs.h" + +srs_t *srs = NULL; +uschar *srs_db_forward = NULL; +uschar *srs_db_reverse = NULL; + + +/* srs_init just initialises libsrs and creates (if necessary) + an srs object to use for all srs calls in this instance */ + +int eximsrs_init() +{ + int co; + uschar *list = srs_config; + char secret_buf[32]; + char *secret; + char sbuf[4]; + char *sbufp; + int hashlen, maxage; + + + if(!srs) + { + // Check config + if(!srs_config) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error"); + return DEFER; + } + + // Get config + co = 0; + if((secret = string_nextinlist(&list, &co, secret_buf, + sizeof(secret_buf))) == NULL) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error: No secret specified"); + return DEFER; + } + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) == NULL) + maxage = 31; + else + maxage = atoi(sbuf); + if(maxage < 0 || maxage > 365) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error: Invalid maximum timestamp age"); + return DEFER; + } + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) == NULL) + hashlen = 6; + else + hashlen = atoi(sbuf); + if(hashlen < 1 || hashlen > 20) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error: Invalid hash length"); + return DEFER; + } + + + if((srs = srs_open(secret, strnlen(secret, sizeof(secret)), + maxage, hashlen, hashlen)) == NULL) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "Failed to allocate SRS memory"); + return DEFER; + } + + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) != NULL) + srs_set_option(srs, SRS_OPTION_USETIMESTAMP, atoi(sbuf)); + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) != NULL) + srs_set_option(srs, SRS_OPTION_USEHASH, atoi(sbuf)); + + DEBUG(D_any) + debug_printf("SRS initialized\n"); + } + + return OK; +} + + +int eximsrs_done() +{ + if(srs) + srs_close(srs); + + srs = NULL; + + return OK; +} + + +int eximsrs_forward(uschar **result, uschar *orig_sender, uschar *domain) +{ + char res[512]; + int n; + + if((n = srs_forward(srs, orig_sender, domain, res, sizeof(res))) & SRS_RESULT_FAIL) + { + DEBUG(D_any) + debug_printf("srs_forward failed (%s, %s): %s\n", orig_sender, domain, srs_geterrormsg(n)); + return DEFER; + } + + *result = string_copy(res); + return OK; +} + + +int eximsrs_reverse(uschar **result, uschar *address) +{ + char res[512]; + int n; + + if((n = srs_reverse(srs, address, res, sizeof(res))) & SRS_RESULT_FAIL) + { + DEBUG(D_any) + debug_printf("srs_reverse failed (%s): %s\n", address, srs_geterrormsg(n)); + if(n == SRS_RESULT_NOTSRS || n == SRS_RESULT_BADSRS) + return DECLINE; + if(n == SRS_RESULT_BADHASH || n == SRS_RESULT_BADTIMESTAMP || n == SRS_RESULT_TIMESTAMPEXPIRED) + return FAIL; + return DEFER; + } + + *result = string_copy(res); + return OK; +} + + +int eximsrs_db_set(BOOL reverse, uschar *srs_db) +{ + if(reverse) + srs_db_reverse = string_copy(srs_db); + else + srs_db_forward = string_copy(srs_db); + + if(srs_set_db_functions(srs, eximsrs_db_insert, eximsrs_db_lookup) * SRS_RESULT_FAIL) + return DEFER; + + return OK; +} + + +srs_result eximsrs_db_insert(srs_t *srs, char *data, uint data_len, char *result, uint result_len) +{ + uschar *res; + char buf[64]; + + srs_db_address = string_copyn(data, data_len); + if(srs_generate_unique_id(srs, srs_db_address, buf, 64) & SRS_RESULT_FAIL) + return DEFER; + + srs_db_key = string_copyn(buf, 16); + + if((res = expand_string(srs_db_forward)) == NULL) + return SRS_RESULT_DBERROR; + + if(result_len < 17) + return SRS_RESULT_DBERROR; + + strncpy(result, srs_db_key, result_len); + + return SRS_RESULT_OK; +} + + +srs_result eximsrs_db_lookup(srs_t *srs, char *data, uint data_len, char *result, uint result_len) +{ + uschar *res; + + srs_db_key = string_copyn(data, data_len); + if((res = expand_string(srs_db_reverse)) == NULL) + return SRS_RESULT_DBERROR; + + if(Ustrlen(res) >= result_len) + return SRS_RESULT_ADDRESSTOOLONG; + + strncpy(result, res, result_len); + + return SRS_RESULT_OK; +} + + +#endif + diff -urN exim-4.41-orig/src/srs.h exim-4.41/src/srs.h --- exim-4.41-orig/src/srs.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/srs.h Thu Jul 22 16:31:13 2004 @@ -0,0 +1,33 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is an extension to Exim and is not part of the standard + Exim distribution */ + +/* ©2004 Miles Wilton */ + +/* License: GPL */ + +#ifndef __SRS_H__ + +#define __SRS_H__ 1 + + +#ifdef SRS + +#include "mytypes.h" +#include + +int eximsrs_init(); +int eximsrs_done(); +int eximsrs_forward(uschar **result, uschar *orig_sender, uschar *domain); +int eximsrs_reverse(uschar **result, uschar *address); +int eximsrs_db(BOOL reverse, uschar *srs_db); + +srs_result eximsrs_db_insert(srs_t *srs, char *data, uint data_len, char *result, uint result_len); +srs_result eximsrs_db_lookup(srs_t *srs, char *data, uint data_len, char *result, uint result_len); + +#endif + +#endif diff -urN exim-4.41-orig/src/structs.h exim-4.41/src/structs.h --- exim-4.41-orig/src/structs.h Thu Jul 22 10:46:53 2004 +++ exim-4.41/src/structs.h Thu Jul 22 16:31:13 2004 @@ -219,6 +219,9 @@ uschar *driver_name; /* Must be first */ uschar *address_data; /* Arbitrary data */ +#ifdef BRIGHTMAIL + uschar *bmi_rule; /* Brightmail AntiSpam rule checking */ +#endif uschar *cannot_route_message; /* Used when routing fails */ uschar *condition; /* General condition */ uschar *current_directory; /* For use during delivery */ @@ -247,6 +250,11 @@ uschar *transport_name; /* Transport name */ BOOL address_test; /* Use this router when testing addresses */ +#ifdef BRIGHTMAIL + BOOL bmi_deliver_alternate; /* TRUE => BMI said that message should be delivered to alternate location */ + BOOL bmi_deliver_default; /* TRUE => BMI said that message should be delivered to default location */ + BOOL bmi_dont_deliver; /* TRUE => BMI said that message should not be delivered at all */ +#endif BOOL expn; /* Use this router when processing EXPN */ BOOL caseful_local_part; /* TRUE => don't lowercase */ BOOL check_local_user; /* TRUE => check local user */ diff -urN exim-4.41-orig/src/tnef.c exim-4.41/src/tnef.c --- exim-4.41-orig/src/tnef.c Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/tnef.c Thu Jul 22 16:31:13 2004 @@ -0,0 +1,757 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/* Code for unpacking TNEF containers. Called from demime.c. */ + +/*************************************************************************** + * tnef2txt +* A program to decode application/ms-tnef MIME attachments into text +* for those fortunate enough not to be running either a Microsoft +* operating system or mailer. +* + * 18/10/2001 +* Brutally cropped by Paul L Daniels (pldaniels@pldaniels.com) in order +* to accommodate the needs of ripMIME/Xamime/Inflex without carrying too +* much excess baggage. +* + * Brandon Long (blong@uiuc.edu), April 1997 +* 1.0 Version +* Supports most types, but doesn't decode properties. Maybe some other +* time. +* + * 1.1 Version (7/1/97) +* Supports saving of attAttachData to a file given by attAttachTitle +* start of property decoding support +* + * 1.2 Version (7/19/97) +* Some architectures don't like reading 16/32 bit data on unaligned +* boundaries. Fixed, losing efficiency, but this doesn't really +* need efficiency anyways. (Still...) +* Also, the #pragma pack from the MSVC include file wasn't liked +* by most Unix compilers, replaced with a GCCism. This should work +* with GCC, but other compilers I don't know. +* + * 1.3 Version (7/22/97) +* Ok, take out the DTR over the stream, now uses read_16. +* + * NOTE: THIS SOFTWARE IS FOR YOUR PERSONAL GRATIFICATION ONLY. I DON'T +* IMPLY IN ANY LEGAL SENSE THAT THIS SOFTWARE DOES ANYTHING OR THAT IT WILL +* BE USEFULL IN ANY WAY. But, you can send me fixes to it, I don't mind. +***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "tnef.h" + + +#define VERSION "pldtnef/0.0.1" + +int _TNEF_syslogging = 0; +int _TNEF_stderrlogging = 0; +int _TNEF_verbose = 0; +int _TNEF_debug = 0; + +int Verbose = FALSE; +int SaveData = FALSE; + +char _TNEF_path[1024]=""; + +uint8 *tnef_home; +uint8 *tnef_limit; + +/*------------------------------------------------------------------------ +Procedure: TNEF_set_path ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_set_path( char *path ) +{ + snprintf(_TNEF_path,1023,"%s",path); + + return 0; +} + + +/*------------------------------------------------------------------------ +Procedure: TNEF_set_verbosity ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_set_verbosity( int level ) +{ + _TNEF_verbose = level; + return _TNEF_verbose; +} + + + + +/*------------------------------------------------------------------------ +Procedure: TNEF_set_debug ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_set_debug( int level ) +{ + _TNEF_debug = level; + TNEF_set_verbosity( level ); + return _TNEF_debug; +} + + + +/*------------------------------------------------------------------------ +Procedure: TNEF_set_syslogging ID:1 +Purpose: Turns on/off the syslog feature for TNEF error messages +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_set_syslogging( int level ) +{ + _TNEF_syslogging = level; + return _TNEF_syslogging; +} + + + + +/*------------------------------------------------------------------------ +Procedure: TNEF_set_stderrlogging ID:1 +Purpose: Turns on/off the stderr feature for TNEF error messages +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_set_stderrlogging( int level ) +{ + _TNEF_stderrlogging = level; + return _TNEF_stderrlogging; +} + + +/* Some systems don't like to read unaligned data */ +/*------------------------------------------------------------------------ +Procedure: read_32 ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +uint32 read_32(uint8 *tsp) +{ + uint8 a,b,c,d; + uint32 ret; + + if (tsp+4 > tnef_limit) + { + if ((_TNEF_verbose)||(_TNEF_stderrlogging)||(_TNEF_debug)) fprintf(stderr,"TNEF read_32() Attempting to read past end\n"); + return -1; + } + + a = *tsp; + b = *(tsp+1); + c = *(tsp+2); + d = *(tsp+3); + + ret = long_little_endian(a<<24 | b<<16 | c<<8 | d); + + return ret; +} + +/*------------------------------------------------------------------------ +Procedure: read_16 ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +uint16 read_16(uint8 *tsp) +{ + uint8 a,b; + uint16 ret; + + if (tsp+2 > tnef_limit) + { + if ((_TNEF_verbose)||(_TNEF_stderrlogging)||(_TNEF_debug)) fprintf(stderr,"TNEF read_16() Attempting to read past end\n"); + return -1; + } + + + a = *tsp; + b = *(tsp + 1); + + ret = little_endian(a<<8 | b); + + return ret; +} + + + +/*------------------------------------------------------------------------ +Procedure: make_string ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +char *make_string(uint8 *tsp, int size) +{ + static char s[256] = ""; + int len = (size>sizeof(s)-1) ? sizeof(s)-1 : size; + + strncpy(s,(char *)tsp, len); + s[len] = '\0'; + return s; +} + + +/*------------------------------------------------------------------------ +Procedure: handle_props ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ + +int save_attach_data(char *, uint8 *, uint32); + +int handle_props(uint8 *tsp) +{ + int bytes = 0; + uint32 num_props = 0; + uint32 x = 0; + + + num_props = read_32(tsp); + bytes += sizeof(num_props); + + while (x < num_props) + { + uint32 prop_tag; + uint32 num; + char filename[256]; + static int file_num = 0; + + prop_tag = read_32(tsp+bytes); + bytes += sizeof(prop_tag); + + switch (prop_tag & PROP_TYPE_MASK) + { + case PT_BINARY: + num = read_32(tsp+bytes); + bytes += sizeof(num); + num = read_32(tsp+bytes); + bytes += sizeof(num); + if (prop_tag == PR_RTF_COMPRESSED) + { + sprintf (filename, "XAM_%d.rtf", file_num); + file_num++; + save_attach_data(filename, tsp+bytes, num); + } + /* num + PAD */ + bytes += num + ((num % 4) ? (4 - num%4) : 0); + break; + case PT_STRING8: + num = read_32(tsp+bytes); + bytes += sizeof(num); + num = read_32(tsp+bytes); + bytes += sizeof(num); + make_string(tsp+bytes,num); + bytes += num + ((num % 4) ? (4 - num%4) : 0); + break; + case PT_UNICODE: + case PT_OBJECT: + break; + case PT_I2: + bytes += 2; + break; + case PT_LONG: + bytes += 4; + break; + case PT_R4: + bytes += 4; + break; + case PT_DOUBLE: + bytes += 8; + break; + case PT_CURRENCY: + case PT_APPTIME: + case PT_ERROR: + bytes += 4; + break; + case PT_BOOLEAN: + bytes += 4; + break; + case PT_I8: + bytes += 8; + case PT_SYSTIME: + bytes += 8; + break; + } + x++; + } + + return 0; +} + + + + +/*------------------------------------------------------------------------ +Procedure: save_attach_data ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int save_attach_data(char *title, uint8 *tsp, uint32 size) +{ + FILE *out; + char filename[1024]; + + /* + if ((*tsp +size) > _TNEF_size) + { + return -1; + } + */ + snprintf(filename,1023,"%s/%s",_TNEF_path,title); + + out = fopen(filename, "w"); + if (!out) + { + if (_TNEF_stderrlogging > 0) fprintf(stderr, "Error openning file %s for writing\n", filename); + return -1; + } + + fwrite(tsp, sizeof(uint8), size, out); + fclose(out); + return 0; +} + + + + +/*------------------------------------------------------------------------ +Procedure: default_handler ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int default_handler(uint32 attribute, uint8 *tsp, uint32 size) +{ + uint16 type = ATT_TYPE(attribute); + + switch (type) { + case atpTriples: + break; + case atpString: + case atpText: + break; + case atpDate: + break; + case atpShort: + break; + case atpLong: + break; + case atpByte: + break; + case atpWord: + break; + case atpDword: + break; + default: + break; + } + return 0; + +} + + + + +/*------------------------------------------------------------------------ +Procedure: read_attribute ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int read_attribute(uint8 *tsp) +{ + + int bytes = 0, header = 0; + uint32 attribute; + uint8 component = 0; + uint32 size = 0; + uint16 checksum = 0; + static char attach_title[256] = { + 0 }; + static uint32 attach_size = 0; + static uint32 attach_loc = 0; + + /* What component are we look at? */ + component = *tsp; + + bytes += sizeof(uint8); + + /* Read the attributes of this component */ + + if (_TNEF_debug) fprintf(stderr,"read_attribute: Reading Attribute...\n"); + attribute = read_32(tsp+bytes); + if (attribute == -1) return -1; + bytes += sizeof(attribute); + + /* Read the size of the information we have to read */ + + if (_TNEF_debug) fprintf(stderr,"read_attribute: Reading Size...\n"); + size = read_32(tsp+bytes); + if (size == -1) return -1; + bytes += sizeof(size); + + /* The header size equals the sum of all the things we've read + so far. */ + + header = bytes; + + /* The is a bit of a tricky one [if you're being slow + it moves the number of bytes ahead by the amount of data of + the attribute we're about to read, so that for next + "read_attribute()" + call starts in the right place. + */ + + bytes += size; + + /* Read in the checksum for this component + + AMMENDMENT - 19/07/02 - 17H01 + Small code change to deal with strange sitations that occur with non + english characters. - Submitted by wtcheuk@netvigator.com @ 19/07/02 + */ + + if ( bytes < 0 ) return -1; + + /* --END of ammendment. */ + + if (_TNEF_debug) fprintf(stderr,"read_attribute: Reading Checksum...(offset %d, bytes=%d)\n", tsp -tnef_home, bytes); + checksum = read_16(tsp+bytes); + bytes += sizeof(checksum); + + if (_TNEF_debug) fprintf(stderr,"Decoding attribute %d\n",attribute); + + switch (attribute) { + case attNull: + default_handler(attribute, tsp+header, size); + break; + case attFrom: + default_handler(attribute, tsp+header, size); + break; + case attSubject: + break; + case attDateSent: + break; + case attDateRecd: + break; + case attMessageStatus: + break; + case attMessageClass: + break; + case attMessageID: + break; + case attParentID: + break; + case attConversationID: + break; + case attBody: + default_handler(attribute, tsp+header, size); + break; + case attPriority: + break; + case attAttachData: + attach_size=size; + attach_loc =(int)tsp+header; + if (SaveData && strlen(attach_title)>0 && attach_size > 0) { + if (!save_attach_data(attach_title, (uint8 *)attach_loc,attach_size)) + { + if (_TNEF_verbose) fprintf(stdout,"Decoding %s\n", attach_title); + } + else + { + if (_TNEF_syslogging > 0) syslog(1,"TNEF: Error saving attachment %s\n",attach_title); + } + } + break; + case attAttachTitle: + strncpy(attach_title, make_string(tsp+header,size),255); + if (SaveData && strlen(attach_title)>0 && attach_size > 0) { + if (!save_attach_data(attach_title, (uint8 *)attach_loc,attach_size)) + { + if (_TNEF_verbose) fprintf(stdout,"Decoding %s\n", attach_title); + } + else + { + if (_TNEF_syslogging > 0) syslog(1,"TNEF: Error saving attachment %s\n",attach_title); + } + } + break; + case attAttachMetaFile: + default_handler(attribute, tsp+header, size); + break; + case attAttachCreateDate: + break; + case attAttachModifyDate: + break; + case attDateModified: + break; + case attAttachTransportFilename: + default_handler(attribute, tsp+header, size); + break; + case attAttachRenddata: + attach_title[0]=0; + attach_size=0; + attach_loc=0; + default_handler(attribute, tsp+header, size); + break; + case attMAPIProps: + handle_props(tsp+header); + break; + case attRecipTable: + default_handler(attribute, tsp+header, size); + break; + case attAttachment: + default_handler(attribute, tsp+header, size); + break; + case attTnefVersion: + { + uint32 version; + version = read_32(tsp+header); + if (version == -1) return -1; + } + break; + case attOemCodepage: + default_handler(attribute, tsp+header, size); + break; + case attOriginalMessageClass: + break; + case attOwner: + default_handler(attribute, tsp+header, size); + break; + case attSentFor: + default_handler(attribute, tsp+header, size); + break; + case attDelegate: + default_handler(attribute, tsp+header, size); + break; + case attDateStart: + break; + case attDateEnd: + break; + case attAidOwner: + default_handler(attribute, tsp+header, size); + break; + case attRequestRes: + default_handler(attribute, tsp+header, size); + break; + default: + default_handler(attribute, tsp+header, size); + break; + } + return bytes; + +} + + + + +/*------------------------------------------------------------------------ +Procedure: decode_tnef ID:1 +Purpose: +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_decode_tnef(uint8 *tnef_stream, int size) +{ + + int ra_response; + uint8 *tsp; + + if (_TNEF_debug) fprintf(stderr,"TNEF_decode_tnef: Start. Size = %d\n",size); + + if (size < 4) + { + if (_TNEF_debug) fprintf(stderr,"TNEF_decode_tnef: Skipping short file\n"); + return 0; + } + + /* TSP == TNEF Stream Pointer (well memory block actually!) + */ + tsp = tnef_stream; + + /* Read in the signature of this TNEF + */ + if (TNEF_SIGNATURE == read_32(tsp)) + { + if (_TNEF_debug) fprintf(stderr,"TNEF signature is good\n"); + } + else + { + if (_TNEF_stderrlogging > 0) fprintf(stderr,"TNEF_decode_tnef: Bad TNEF signature, expecting %x got %lx\n",TNEF_SIGNATURE,read_32(tsp)); + } + + /* Move tsp pointer along + */ + tsp += sizeof(TNEF_SIGNATURE); + + /* This extra check is here just in case we're running with + * _TNEF_debug set and try to calculate TNEF Attach Key + * when we shouldn't. + */ + if (tsp + sizeof(uint16) - tnef_stream > size) + { + if (_TNEF_debug) fprintf(stderr,"TNEF_decode_tnef: Skipping short file\n"); + return 0; + } + + if (_TNEF_debug) fprintf(stderr,"TNEF Attach Key: %x\n",read_16(tsp)); + /* Move tsp pointer along + */ + tsp += sizeof(uint16); + + /* While we still have more bytes to process, + go through entire memory block and extract + all the required attributes and files + */ + if (_TNEF_debug) fprintf(stderr,"TNEF - Commence reading attributes\n"); + while ((tsp - tnef_stream) < size) + { + if (_TNEF_debug) fprintf(stderr,"Offset = %d\n",tsp -tnef_home); + ra_response = read_attribute(tsp); + if ( ra_response > 0 ) + { + tsp += ra_response; + } else { + + /* Must find out /WHY/ this happens, and, how to rectify the issue. */ + + tsp++; + if (_TNEF_debug) fprintf(stderr,"TNEF - Attempting to read attribute resulted in a sub-zero response, ending decoding to be safe\n"); + break; + } + } + + if (_TNEF_debug) fprintf(stderr,"TNEF - DONE.\n"); + + return 0; +} + + + + + + +/*------------------------------------------------------------------------ +Procedure: TNEF_main ID:1 +Purpose: Decodes a given TNEF encoded file +Input: +Output: +Errors: +------------------------------------------------------------------------*/ +int TNEF_main( char *filename ) +{ + FILE *fp; + struct stat sb; + uint8 *tnef_stream; + int size, nread; + + if (_TNEF_debug) fprintf(stderr,"TNEF_main: Start, decoding %s\n",filename); + + SaveData = TRUE; + + /* Test to see if the file actually exists + */ + if (stat(filename,&sb) == -1) + { + if (_TNEF_stderrlogging > 0) fprintf(stderr,"Error stating file %s (%s)\n", filename,strerror(errno)); + return -1; + } + + /* Get the filesize */ + + size = sb.st_size; + + /* Allocate enough memory to read in the ENTIRE file + FIXME - This could be a real consumer if multiple + instances of TNEF decoding is going on + */ + + tnef_home = tnef_stream = (uint8 *)malloc(size); + tnef_limit = tnef_home +size; + + /* If we were unable to allocate enough memory, then we + should report this */ + + if (tnef_stream == NULL) + { + if (_TNEF_stderrlogging > 0) fprintf(stderr,"Error allocating %d bytes for loading file (%s)\n", size,strerror(errno)); + return -1; + } + + /* Attempt to open up the TNEF encoded file... if it fails + then report the failed condition to syslog */ + + if ((fp = fopen(filename,"r")) == NULL) + { + if (_TNEF_stderrlogging > 0) fprintf(stderr,"Error opening file %s for reading (%s)\n", filename,strerror(errno)); + return -1; + } + + /* Attempt to read in the entire file */ + + nread = fread(tnef_stream, sizeof(uint8), size, fp); + + if (_TNEF_debug) fprintf(stderr,"TNEF: Read %d bytes\n",nread); + + /* If we did not read in all the bytes, then let syslogs know! */ + + if (nread < size) + { + return -1; + } + + /* Close the file */ + + fclose(fp); + + /* Proceed to decode the file */ + + TNEF_decode_tnef(tnef_stream,size); + + + if (_TNEF_debug) fprintf(stderr,"TNEF - finished decoding.\n"); + + return 0; +} + + +/* --------------------------END. */ + + + diff -urN exim-4.41-orig/src/tnef.h exim-4.41/src/tnef.h --- exim-4.41-orig/src/tnef.h Thu Jan 1 01:00:00 1970 +++ exim-4.41/src/tnef.h Thu Jul 22 16:31:13 2004 @@ -0,0 +1,1841 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* This file is part of the exiscan-acl content scanner +patch. It is NOT part of the standard exim distribution. */ + +/*************************************************************************** + * + * config.h for tnef decoder by Brandon Long + * Based on config.h from S3MOD by Dan Marks and David Jeske + * + * (C) 1994,1995 By Daniel Marks and David Jeske + * + * While we retain the copyright to this code, this source code is FREE. + * You may use it in any way you wish, in any product you wish. You may + * NOT steal the copyright for this code from us. + * + * We respectfully ask that you email one of us, if possible, if you + * produce something significant with this code, or if you have any bug + * fixes to contribute. We also request that you give credit where + * credit is due if you include part of this code in a program of your own. + * + *************************************************************************** + * + * config.h - compile time configuration options and system specific defines + * + */ + +/* 2003-02-03 Merged all TNEF and MAPI related headers in this file to reduce + clutter + - Tom Kistner +*/ + +#include + +#ifndef _CONFIG_H +#define _CONFIG_H 1 + +/***************************************************************************/ +/* The following are system specific settings */ +/***************************************************************************/ + +#if defined(SUN) +#define BIT_32 +#define ___TNEF_BYTE_ORDER 4321 +#undef NEAR_FAR_PTR + +#elif defined (HPUX) +#define BIT_32 +#define ___TNEF_BYTE_ORDER 4321 +#undef NEAR_FAR_PTR + +#elif defined(DEC) +#undef NEAR_FAR_PTR + +#elif defined(__sgi) +#define BIT_32 +#define ___TNEF_BYTE_ORDER 4321 +#undef NEAR_FAR_PTR + +#elif defined(AIX) +#undef NEAR_FAR_PTR +#define ___TNEF_BYTE_ORDER 4321 +#define BIT_32 + +#elif defined(LINUX) +#define BIT_32 +#undef NEAR_FAR_PTR + +#elif defined(MSDOS) +#define NEAR_FAR_PTR +#undef BIT_32 + +#else +#undef NEAR_FAR_PTR +#define BIT_32 + + +#endif /* OS/MACH TYPE */ + +/***************************************************************************/ +/* 16/32 Bit and Byte Order hacks */ +/***************************************************************************/ + +#ifdef BIT_32 +typedef short int int16; +typedef unsigned short int uint16; +typedef int int32; +typedef unsigned int uint32; +/* typedef char int8; */ +typedef unsigned char uint8; +#else +typedef int int16; +typedef unsigned int uint16; +typedef long int int32; +typedef unsigned long int uint32; +typedef char int8; +typedef unsigned char uint8; +#endif /* BIT_32 */ + +#ifndef WIN32_TYPES +#define ULONG uint32 +#define SCODE uint32 +#define FAR +#define LPVOID void * +#define WORD uint16 +#define DWORD uint32 +#define LONG int32 +#define BYTE uint8 +#endif /* !WIN32_TYPES */ + +#define endian_switch(x) (((((uint16)(x)) & 0xFF00) >> 8) | \ + ((((uint16)(x)) & 0xFF) << 8)) + +#define long_endian_switch(x) ( ((((uint32)(x)) & 0xFF00UL) << 8) | \ + ((((uint32)(x)) & 0xFFUL) << 24) | \ + ((((uint32)(x)) & 0xFF0000UL) >> 8) | \ + ((((uint32)(x)) & 0xFF000000UL) >> 24)) + +#if ___TNEF_BYTE_ORDER == 4321 +#define big_endian(x) (x) +#define long_big_endian(x) (x) +#define little_endian(x) (endian_switch(x)) +#define long_little_endian(x) (long_endian_switch(x)) +#else +#define big_endian(x) (endian_switch(x)) +#define long_big_endian(x) (long_endian_switch(x)) +#define little_endian(x) (x) +#define long_little_endian(x) (x) +#endif /* ___TNEF_BYTE_ORDER */ + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + + +#endif /* _CONFIG_H */ +/* + * Taken from the Win32 SDK or the MSVC4 include files, I'm not sure which. + * The document describing the TNEF format alludes to this document for more + * information. This file was stripped a bit to allow it to compile with + * GCC and without random other Windows header files so it could be used + * to decode TNEF bitstreams with tnef2txt. + * + * T N E F . H + * + * + * This file contains structure and function definitions for the + * MAPI implementation of the Transport Neutral Encapsilation Format + * used by MAPI providers for the neutral serialization of a MAPI + * message. This implementation sits on top of the IStream object as + * documented in the OLE 2 Specs. + * + * Copyright 1986-1996 Microsoft Corporation. All Rights Reserved. + */ + +#ifndef TNEF_H +#define TNEF_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef BEGIN_INTERFACE +#define BEGIN_INTERFACE +#endif + +#ifndef MAPI_DIM +#define MAPI_DIM 1 +#endif + +#define TNTNoffsetof(s,m) (unsigned long)&(((s *)0)->m) + +/* ------------------------------------ */ +/* TNEF Problem and TNEF Problem Arrays */ +/* ------------------------------------ */ + +typedef struct _STnefProblem +{ + ULONG ulComponent; + ULONG ulAttribute; + ULONG ulPropTag; + SCODE scode; +} STnefProblem; + +typedef struct _STnefProblemArray +{ + ULONG cProblem; + STnefProblem aProblem[MAPI_DIM]; +} STnefProblemArray, FAR * LPSTnefProblemArray; + +#if 0 +#define CbNewSTnefProblemArray(_cprob) \ + (TNoffsetof(STnefProblemArray,aProblem) + (_cprob)*sizeof(STnefProblem)) +#define CbSTnefProblemArray(_lparray) \ + (TNoffsetof(STnefProblemArray,aProblem) + \ + (UINT) ((_lparray)->cProblem*sizeof(STnefProblem))) +#endif + +/* Pointers to TNEF Interface ---------------------------------------- */ + +#if 0 +DECLARE_MAPI_INTERFACE_PTR(ITnef, LPITNEF); +#endif + +/* OpenTNEFStream */ + +#define TNEF_DECODE ((ULONG) 0) +#define TNEF_ENCODE ((ULONG) 2) + +#define TNEF_PURE ((ULONG) 0x00010000) +#define TNEF_COMPATIBILITY ((ULONG) 0x00020000) +#define TNEF_BEST_DATA ((ULONG) 0x00040000) +#define TNEF_COMPONENT_ENCODING ((ULONG) 0x80000000) + +/* AddProps, ExtractProps */ + +#define TNEF_PROP_INCLUDE ((ULONG) 0x00000001) +#define TNEF_PROP_EXCLUDE ((ULONG) 0x00000002) +#define TNEF_PROP_CONTAINED ((ULONG) 0x00000004) +#define TNEF_PROP_MESSAGE_ONLY ((ULONG) 0x00000008) +#define TNEF_PROP_ATTACHMENTS_ONLY ((ULONG) 0x00000010) +#define TNEF_PROP_CONTAINED_TNEF ((ULONG) 0x00000040) + +/* FinishComponent */ + +#define TNEF_COMPONENT_MESSAGE ((ULONG) 0x00001000) +#define TNEF_COMPONENT_ATTACHMENT ((ULONG) 0x00002000) + +#if 0 +#define MAPI_ITNEF_METHODS(IPURE) \ + MAPIMETHOD(AddProps) \ + (THIS_ ULONG ulFlags, \ + ULONG ulElemID, \ + LPVOID lpvData, \ + LPSPropTagArray lpPropList) IPURE; \ + MAPIMETHOD(ExtractProps) \ + (THIS_ ULONG ulFlags, \ + LPSPropTagArray lpPropList, \ + LPSTnefProblemArray FAR * lpProblems) IPURE; \ + MAPIMETHOD(Finish) \ + (THIS_ ULONG ulFlags, \ + WORD FAR * lpKey, \ + LPSTnefProblemArray FAR * lpProblems) IPURE; \ + MAPIMETHOD(OpenTaggedBody) \ + (THIS_ LPMESSAGE lpMessage, \ + ULONG ulFlags, \ + LPSTREAM FAR * lppStream) IPURE; \ + MAPIMETHOD(SetProps) \ + (THIS_ ULONG ulFlags, \ + ULONG ulElemID, \ + ULONG cValues, \ + LPSPropValue lpProps) IPURE; \ + MAPIMETHOD(EncodeRecips) \ + (THIS_ ULONG ulFlags, \ + LPMAPITABLE lpRecipientTable) IPURE; \ + MAPIMETHOD(FinishComponent) \ + (THIS_ ULONG ulFlags, \ + ULONG ulComponentID, \ + LPSPropTagArray lpCustomPropList, \ + LPSPropValue lpCustomProps, \ + LPSPropTagArray lpPropList, \ + LPSTnefProblemArray FAR * lpProblems) IPURE; \ + +#undef INTERFACE +#define INTERFACE ITnef +DECLARE_MAPI_INTERFACE_(ITnef, IUnknown) +{ + BEGIN_INTERFACE + MAPI_IUNKNOWN_METHODS(PURE) + MAPI_ITNEF_METHODS(PURE) +}; + +STDMETHODIMP OpenTnefStream( + LPVOID lpvSupport, + LPSTREAM lpStream, + LPTSTR lpszStreamName, + ULONG ulFlags, + LPMESSAGE lpMessage, + WORD wKeyVal, + LPITNEF FAR * lppTNEF); + +typedef HRESULT (STDMETHODCALLTYPE FAR * LPOPENTNEFSTREAM) ( + LPVOID lpvSupport, + LPSTREAM lpStream, + LPTSTR lpszStreamName, + ULONG ulFlags, + LPMESSAGE lpMessage, + WORD wKeyVal, + LPITNEF FAR * lppTNEF); + +STDMETHODIMP OpenTnefStreamEx( + LPVOID lpvSupport, + LPSTREAM lpStream, + LPTSTR lpszStreamName, + ULONG ulFlags, + LPMESSAGE lpMessage, + WORD wKeyVal, + LPADRBOOK lpAdressBook, + LPITNEF FAR * lppTNEF); + +typedef HRESULT (STDMETHODCALLTYPE FAR * LPOPENTNEFSTREAMEX) ( + LPVOID lpvSupport, + LPSTREAM lpStream, + LPTSTR lpszStreamName, + ULONG ulFlags, + LPMESSAGE lpMessage, + WORD wKeyVal, + LPADRBOOK lpAdressBook, + LPITNEF FAR * lppTNEF); + +STDMETHODIMP GetTnefStreamCodepage ( + LPSTREAM lpStream, + ULONG FAR * lpulCodepage, + ULONG FAR * lpulSubCodepage); + +typedef HRESULT (STDMETHODCALLTYPE FAR * LPGETTNEFSTREAMCODEPAGE) ( + LPSTREAM lpStream, + ULONG FAR * lpulCodepage, + ULONG FAR * lpulSubCodepage); + +#define OPENTNEFSTREAM "OpenTnefStream" +#define OPENTNEFSTREAMEX "OpenTnefStreamEx" +#define GETTNEFSTREAMCODEPAGE "GetTnefStreamCodePage" +#endif + +/* -------------------------- */ +/* TNEF Signature and Version */ +/* -------------------------- */ + +#define MAKE_TNEF_VERSION(_mj,_mn) (((ULONG)(0x0000FFFF & _mj) << 16) | (ULONG)(0x0000FFFF & _mn)) +#define TNEF_SIGNATURE ((ULONG) 0x223E9F78) +#define TNEF_VERSION ((ULONG) MAKE_TNEF_VERSION(1,0)) + + +/* ------------------------------------------- */ +/* TNEF Down-level Attachment Types/Structures */ +/* ------------------------------------------- */ + +typedef WORD ATYP; +enum { atypNull, atypFile, atypOle, atypPicture, atypMax }; + +#define MAC_BINARY ((DWORD) 0x00000001) + +typedef struct _renddata +{ + ATYP atyp; + ULONG ulPosition; + WORD dxWidth; + WORD dyHeight; + DWORD dwFlags; + +} RENDDATA, *PRENDDATA; + +/* ----------------------------------- */ +/* TNEF Down-level Date/Time Structure */ +/* ----------------------------------- */ + +typedef struct _dtr +{ + WORD wYear; + WORD wMonth; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wDayOfWeek; + +} DTR; + + +/* ----------------------------- */ +/* TNEF Down-level Message Flags */ +/* ----------------------------- */ + +#define fmsNull ((BYTE) 0x00) +#define fmsModified ((BYTE) 0x01) +#define fmsLocal ((BYTE) 0x02) +#define fmsSubmitted ((BYTE) 0x04) +#define fmsRead ((BYTE) 0x20) +#define fmsHasAttach ((BYTE) 0x80) + + +/* ----------------------------------------- */ +/* TNEF Down-level Triple Address Structures */ +/* ----------------------------------------- */ + +#define trpidNull ((WORD) 0x0000) +#define trpidUnresolved ((WORD) 0x0001) +#define trpidResolvedNSID ((WORD) 0x0002) +#define trpidResolvedAddress ((WORD) 0x0003) +#define trpidOneOff ((WORD) 0x0004) +#define trpidGroupNSID ((WORD) 0x0005) +#define trpidOffline ((WORD) 0x0006) +#define trpidIgnore ((WORD) 0x0007) +#define trpidClassEntry ((WORD) 0x0008) +#define trpidResolvedGroupAddress ((WORD) 0x0009) +typedef struct _trp +{ + WORD trpid; + WORD cbgrtrp; + WORD cch; + WORD cbRgb; + +} TRP, *PTRP, *PGRTRP, FAR * LPTRP; +#define CbOfTrp(_p) (sizeof(TRP) + (_p)->cch + (_p)->cbRgb) +#define LpszOfTrp(_p) ((LPSTR)(((LPTRP) (_p)) + 1)) +#define LpbOfTrp(_p) (((LPBYTE)(((LPTRP)(_p)) + 1)) + (_p)->cch) +#define LptrpNext(_p) ((LPTRP)((LPBYTE)(_p) + CbOfTrp(_p))) + +typedef DWORD XTYPE; +#define xtypeUnknown ((XTYPE) 0) +#define xtypeInternet ((XTYPE) 6) + +#define cbDisplayName 41 +#define cbEmailName 11 +#define cbSeverName 12 +typedef struct _ADDR_ALIAS +{ + char rgchName[cbDisplayName]; + char rgchEName[cbEmailName]; + char rgchSrvr[cbSeverName]; + ULONG dibDetail; + WORD type; + +} ADDRALIAS, FAR * LPADDRALIAS; +#define cbALIAS sizeof(ALIAS) + +#define cbTYPE 16 +#define cbMaxIdData 200 +typedef struct _NSID +{ + DWORD dwSize; + unsigned char uchType[cbTYPE]; + XTYPE xtype; + LONG lTime; + + union + { + ADDRALIAS alias; + char rgchInterNet[1]; + + } address; + +} NSID, * LPNSID; +#define cbNSID sizeof(NSID) + + +/* -------------------------- */ +/* TNEF Down-level Priorities */ +/* -------------------------- */ + +#define prioLow 3 +#define prioNorm 2 +#define prioHigh 1 + + +/* ------------------------------------- */ +/* TNEF Down-level Attributes/Properties */ +/* ------------------------------------- */ + +#define atpTriples ((WORD) 0x0000) +#define atpString ((WORD) 0x0001) +#define atpText ((WORD) 0x0002) +#define atpDate ((WORD) 0x0003) +#define atpShort ((WORD) 0x0004) +#define atpLong ((WORD) 0x0005) +#define atpByte ((WORD) 0x0006) +#define atpWord ((WORD) 0x0007) +#define atpDword ((WORD) 0x0008) +#define atpMax ((WORD) 0x0009) + +#define LVL_MESSAGE ((BYTE) 0x01) +#define LVL_ATTACHMENT ((BYTE) 0x02) + +#define ATT_ID(_att) ((WORD) ((_att) & 0x0000FFFF)) +#define ATT_TYPE(_att) ((WORD) (((_att) >> 16) & 0x0000FFFF)) +#define ATT(_atp, _id) ((((DWORD) (_atp)) << 16) | ((WORD) (_id))) + +#define attNull ATT( 0, 0x0000) +#define attFrom ATT( atpTriples, 0x8000) /* PR_ORIGINATOR_RETURN_ADDRESS */ +#define attSubject ATT( atpString, 0x8004) /* PR_SUBJECT */ +#define attDateSent ATT( atpDate, 0x8005) /* PR_CLIENT_SUBMIT_TIME */ +#define attDateRecd ATT( atpDate, 0x8006) /* PR_MESSAGE_DELIVERY_TIME */ +#define attMessageStatus ATT( atpByte, 0x8007) /* PR_MESSAGE_FLAGS */ +#define attMessageClass ATT( atpWord, 0x8008) /* PR_MESSAGE_CLASS */ +#define attMessageID ATT( atpString, 0x8009) /* PR_MESSAGE_ID */ +#define attParentID ATT( atpString, 0x800A) /* PR_PARENT_ID */ +#define attConversationID ATT( atpString, 0x800B) /* PR_CONVERSATION_ID */ +#define attBody ATT( atpText, 0x800C) /* PR_BODY */ +#define attPriority ATT( atpShort, 0x800D) /* PR_IMPORTANCE */ +#define attAttachData ATT( atpByte, 0x800F) /* PR_ATTACH_DATA_xxx */ +#define attAttachTitle ATT( atpString, 0x8010) /* PR_ATTACH_FILENAME */ +#define attAttachMetaFile ATT( atpByte, 0x8011) /* PR_ATTACH_RENDERING */ +#define attAttachCreateDate ATT( atpDate, 0x8012) /* PR_CREATION_TIME */ +#define attAttachModifyDate ATT( atpDate, 0x8013) /* PR_LAST_MODIFICATION_TIME */ +#define attDateModified ATT( atpDate, 0x8020) /* PR_LAST_MODIFICATION_TIME */ +#define attAttachTransportFilename ATT( atpByte, 0x9001) /* PR_ATTACH_TRANSPORT_NAME */ +#define attAttachRenddata ATT( atpByte, 0x9002) +#define attMAPIProps ATT( atpByte, 0x9003) +#define attRecipTable ATT( atpByte, 0x9004) /* PR_MESSAGE_RECIPIENTS */ +#define attAttachment ATT( atpByte, 0x9005) +#define attTnefVersion ATT( atpDword, 0x9006) +#define attOemCodepage ATT( atpByte, 0x9007) +#define attOriginalMessageClass ATT( atpWord, 0x0006) /* PR_ORIG_MESSAGE_CLASS */ + +#define attOwner ATT( atpByte, 0x0000) /* PR_RCVD_REPRESENTING_xxx or + PR_SENT_REPRESENTING_xxx */ +#define attSentFor ATT( atpByte, 0x0001) /* PR_SENT_REPRESENTING_xxx */ +#define attDelegate ATT( atpByte, 0x0002) /* PR_RCVD_REPRESENTING_xxx */ +#define attDateStart ATT( atpDate, 0x0006) /* PR_DATE_START */ +#define attDateEnd ATT( atpDate, 0x0007) /* PR_DATE_END */ +#define attAidOwner ATT( atpLong, 0x0008) /* PR_OWNER_APPT_ID */ +#define attRequestRes ATT( atpShort, 0x0009) /* PR_RESPONSE_REQUESTED */ + +#ifdef __cplusplus +} +#endif + +#endif /* defined TNEF_H */ +/* + * M A P I D E F S . H + * + * Definitions used by MAPI clients and service providers. + * + * Copyright 1986-1996 Microsoft Corporation. All Rights Reserved. + */ + +#ifndef MAPIDEFS_H +#define MAPIDEFS_H + + +/* Array dimension for structures with variable-sized arrays at the end. */ + +/* Simple data types */ + + +typedef WORD WCHAR; + +#ifdef UNICODE +typedef WCHAR TCHAR; +#else +typedef char TCHAR; +#endif + +typedef WCHAR * LPWSTR; +typedef const WCHAR * LPCWSTR; +typedef TCHAR * LPTSTR; +typedef const TCHAR * LPCTSTR; +typedef BYTE * LPBYTE; + +typedef ULONG * LPULONG; + +#ifndef __LHANDLE +#define __LHANDLE +typedef unsigned long LHANDLE, * LPLHANDLE; +#endif + +#if !defined(_WINBASE_) && !defined(_FILETIME_) +#define _FILETIME_ +typedef struct _FILETIME +{ + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME, * LPFILETIME; +#endif + +/* + * This flag is used in many different MAPI calls to signify that + * the object opened by the call should be modifiable (MAPI_MODIFY). + * If the flag MAPI_MAX_ACCESS is set, the object returned should be + * returned at the maximum access level allowed. An additional + * property available on the object (PR_ACCESS_LEVEL) uses the same + * MAPI_MODIFY flag to say just what this new access level is. + */ + +#define MAPI_MODIFY ((ULONG) 0x00000001) + +/* + * The following flags are used to indicate to the client what access + * level is permissible in the object. They appear in PR_ACCESS in + * message and folder objects as well as in contents and associated + * contents tables + */ + +#define MAPI_ACCESS_MODIFY ((ULONG) 0x00000001) +#define MAPI_ACCESS_READ ((ULONG) 0x00000002) +#define MAPI_ACCESS_DELETE ((ULONG) 0x00000004) +#define MAPI_ACCESS_CREATE_HIERARCHY ((ULONG) 0x00000008) +#define MAPI_ACCESS_CREATE_CONTENTS ((ULONG) 0x00000010) +#define MAPI_ACCESS_CREATE_ASSOCIATED ((ULONG) 0x00000020) + +/* + * The MAPI_UNICODE flag is used in many different MAPI calls to signify + * that strings passed through the interface are in Unicode (a 16-bit + * character set). The default is an 8-bit character set. + * + * The value fMapiUnicode can be used as the 'normal' value for + * that bit, given the application's default character set. + */ + +#define MAPI_UNICODE ((ULONG) 0x80000000) + +#ifdef UNICODE +#define fMapiUnicode MAPI_UNICODE +#else +#define fMapiUnicode 0 +#endif + +/* successful HRESULT */ +#define hrSuccess 0 + + + +/* Recipient types */ +#ifndef MAPI_ORIG /* also defined in mapi.h */ +#define MAPI_ORIG 0 /* Recipient is message originator */ +#define MAPI_TO 1 /* Recipient is a primary recipient */ +#define MAPI_CC 2 /* Recipient is a copy recipient */ +#define MAPI_BCC 3 /* Recipient is blind copy recipient */ +#define MAPI_P1 0x10000000 /* Recipient is a P1 resend recipient */ +#define MAPI_SUBMITTED 0x80000000 /* Recipient is already processed */ +/* #define MAPI_AUTHORIZE 4 recipient is a CMC authorizing user */ +/*#define MAPI_DISCRETE 0x10000000 Recipient is a P1 resend recipient */ +#endif + +/* Bit definitions for abFlags[0] of ENTRYID */ +#define MAPI_SHORTTERM 0x80 +#define MAPI_NOTRECIP 0x40 +#define MAPI_THISSESSION 0x20 +#define MAPI_NOW 0x10 +#define MAPI_NOTRESERVED 0x08 + +/* Bit definitions for abFlags[1] of ENTRYID */ +#define MAPI_COMPOUND 0x80 + +/* ENTRYID */ +typedef struct +{ + BYTE abFlags[4]; + BYTE ab[MAPI_DIM]; +} ENTRYID, *LPENTRYID; + +#define CbNewENTRYID(_cb) (offsetof(ENTRYID,ab) + (_cb)) +#define CbENTRYID(_cb) (offsetof(ENTRYID,ab) + (_cb)) + +/* Byte-order-independent version of GUID (world-unique identifier) */ +typedef struct _MAPIUID +{ + BYTE ab[16]; +} MAPIUID, * LPMAPIUID; + +/* Note: need to include C run-times (memory.h) to use this macro */ + +#define IsEqualMAPIUID(lpuid1, lpuid2) (!memcmp(lpuid1, lpuid2, sizeof(MAPIUID))) + +/* + * Constants for one-off entry ID: + * The MAPIUID that identifies the one-off provider; + * the flag that defines whether the embedded strings are Unicode; + * the flag that specifies whether the recipient gets TNEF or not. + */ + +#define MAPI_ONE_OFF_UID { 0x81, 0x2b, 0x1f, 0xa4, 0xbe, 0xa3, 0x10, 0x19, 0x9d, 0x6e, 0x00, 0xdd, 0x01, 0x0f, 0x54, 0x02 } +#define MAPI_ONE_OFF_UNICODE 0x8000 +#define MAPI_ONE_OFF_NO_RICH_INFO 0x0001 + +/* Object type */ + +#define MAPI_STORE ((ULONG) 0x00000001) /* Message Store */ +#define MAPI_ADDRBOOK ((ULONG) 0x00000002) /* Address Book */ +#define MAPI_FOLDER ((ULONG) 0x00000003) /* Folder */ +#define MAPI_ABCONT ((ULONG) 0x00000004) /* Address Book Container */ +#define MAPI_MESSAGE ((ULONG) 0x00000005) /* Message */ +#define MAPI_MAILUSER ((ULONG) 0x00000006) /* Individual Recipient */ +#define MAPI_ATTACH ((ULONG) 0x00000007) /* Attachment */ +#define MAPI_DISTLIST ((ULONG) 0x00000008) /* Distribution List Recipient */ +#define MAPI_PROFSECT ((ULONG) 0x00000009) /* Profile Section */ +#define MAPI_STATUS ((ULONG) 0x0000000A) /* Status Object */ +#define MAPI_SESSION ((ULONG) 0x0000000B) /* Session */ +#define MAPI_FORMINFO ((ULONG) 0x0000000C) /* Form Information */ + + +/* + * Maximum length of profile names and passwords, not including + * the null termination character. + */ +#ifndef cchProfileNameMax +#define cchProfileNameMax 64 +#define cchProfilePassMax 64 +#endif + + +/* Property Types */ + +#define MV_FLAG 0x1000 /* Multi-value flag */ + +#define PT_UNSPECIFIED ((ULONG) 0) /* (Reserved for interface use) type doesn't matter to caller */ +#define PT_NULL ((ULONG) 1) /* NULL property value */ +#define PT_I2 ((ULONG) 2) /* Signed 16-bit value */ +#define PT_LONG ((ULONG) 3) /* Signed 32-bit value */ +#define PT_R4 ((ULONG) 4) /* 4-byte floating point */ +#define PT_DOUBLE ((ULONG) 5) /* Floating point double */ +#define PT_CURRENCY ((ULONG) 6) /* Signed 64-bit int (decimal w/ 4 digits right of decimal pt) */ +#define PT_APPTIME ((ULONG) 7) /* Application time */ +#define PT_ERROR ((ULONG) 10) /* 32-bit error value */ +#define PT_BOOLEAN ((ULONG) 11) /* 16-bit boolean (non-zero true) */ +#define PT_OBJECT ((ULONG) 13) /* Embedded object in a property */ +#define PT_I8 ((ULONG) 20) /* 8-byte signed integer */ +#define PT_STRING8 ((ULONG) 30) /* Null terminated 8-bit character string */ +#define PT_UNICODE ((ULONG) 31) /* Null terminated Unicode string */ +#define PT_SYSTIME ((ULONG) 64) /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */ +#define PT_CLSID ((ULONG) 72) /* OLE GUID */ +#define PT_BINARY ((ULONG) 258) /* Uninterpreted (counted byte array) */ +/* Changes are likely to these numbers, and to their structures. */ + +/* Alternate property type names for ease of use */ +#define PT_SHORT PT_I2 +#define PT_I4 PT_LONG +#define PT_FLOAT PT_R4 +#define PT_R8 PT_DOUBLE +#define PT_LONGLONG PT_I8 + +/* + * The type of a MAPI-defined string property is indirected, so + * that it defaults to Unicode string on a Unicode platform and to + * String8 on an ANSI or DBCS platform. + * + * Macros are defined here both for the property type, and for the + * field of the property value structure which should be + * dereferenced to obtain the string pointer. + */ + +#ifdef UNICODE +#define PT_TSTRING PT_UNICODE +#define PT_MV_TSTRING (MV_FLAG|PT_UNICODE) +#define LPSZ lpszW +#define LPPSZ lppszW +#define MVSZ MVszW +#else +#define PT_TSTRING PT_STRING8 +#define PT_MV_TSTRING (MV_FLAG|PT_STRING8) +#define LPSZ lpszA +#define LPPSZ lppszA +#define MVSZ MVszA +#endif + + +/* Property Tags + * + * By convention, MAPI never uses 0 or FFFF as a property ID. + * Use as null values, initializers, sentinels, or what have you. + */ + +#define PROP_TYPE_MASK ((ULONG)0x0000FFFF) /* Mask for Property type */ +#define PROP_TYPE(ulPropTag) (((ULONG)(ulPropTag))&PROP_TYPE_MASK) +#define PROP_ID(ulPropTag) (((ULONG)(ulPropTag))>>16) +#define PROP_TAG(ulPropType,ulPropID) ((((ULONG)(ulPropID))<<16)|((ULONG)(ulPropType))) +#define PROP_ID_NULL 0 +#define PROP_ID_INVALID 0xFFFF +#define PR_NULL PROP_TAG( PT_NULL, PROP_ID_NULL) +#if 0 +#define CHANGE_PROP_TYPE(ulPropTag, ulPropType) \ + (((ULONG)0xFFFF0000 & ulPropTag) | ulPropType) +#endif + + +/* Multi-valued Property Types */ + +#define PT_MV_I2 (MV_FLAG|PT_I2) +#define PT_MV_LONG (MV_FLAG|PT_LONG) +#define PT_MV_R4 (MV_FLAG|PT_R4) +#define PT_MV_DOUBLE (MV_FLAG|PT_DOUBLE) +#define PT_MV_CURRENCY (MV_FLAG|PT_CURRENCY) +#define PT_MV_APPTIME (MV_FLAG|PT_APPTIME) +#define PT_MV_SYSTIME (MV_FLAG|PT_SYSTIME) +#define PT_MV_STRING8 (MV_FLAG|PT_STRING8) +#define PT_MV_BINARY (MV_FLAG|PT_BINARY) +#define PT_MV_UNICODE (MV_FLAG|PT_UNICODE) +#define PT_MV_CLSID (MV_FLAG|PT_CLSID) +#define PT_MV_I8 (MV_FLAG|PT_I8) + +/* Alternate property type names for ease of use */ +#define PT_MV_SHORT PT_MV_I2 +#define PT_MV_I4 PT_MV_LONG +#define PT_MV_FLOAT PT_MV_R4 +#define PT_MV_R8 PT_MV_DOUBLE +#define PT_MV_LONGLONG PT_MV_I8 + +/* + * Property type reserved bits + * + * MV_INSTANCE is used as a flag in table operations to request + * that a multi-valued property be presented as a single-valued + * property appearing in multiple rows. + */ + +#define MV_INSTANCE 0x2000 +#define MVI_FLAG (MV_FLAG | MV_INSTANCE) +#define MVI_PROP(tag) ((tag) | MVI_FLAG) + + + +#endif /* MAPIDEFS_H */ +/* + * M A P I T A G S . H + * + * Property tag definitions for standard properties of MAPI + * objects. + * + * The following ranges should be used for all property IDs. Note that + * property IDs for objects other than messages and recipients should + * all fall in the range 0x3000 to 0x3FFF: + * + * From To Kind of property + * -------------------------------- + * 0001 0BFF MAPI_defined envelope property + * 0C00 0DFF MAPI_defined per-recipient property + * 0E00 0FFF MAPI_defined non-transmittable property + * 1000 2FFF MAPI_defined message content property + * + * 3000 3FFF MAPI_defined property (usually not message or recipient) + * + * 4000 57FF Transport-defined envelope property + * 5800 5FFF Transport-defined per-recipient property + * 6000 65FF User-defined non-transmittable property + * 6600 67FF Provider-defined internal non-transmittable property + * 6800 7BFF Message class-defined content property + * 7C00 7FFF Message class-defined non-transmittable + * property + * + * 8000 FFFE User-defined Name-to-id mapped property + * + * The 3000-3FFF range is further subdivided as follows: + * + * From To Kind of property + * -------------------------------- + * 3000 33FF Common property such as display name, entry ID + * 3400 35FF Message store object + * 3600 36FF Folder or AB container + * 3700 38FF Attachment + * 3900 39FF Address book object + * 3A00 3BFF Mail user + * 3C00 3CFF Distribution list + * 3D00 3DFF Profile section + * 3E00 3FFF Status object + * + * Copyright 1986-1996 Microsoft Corporation. All Rights Reserved. + */ + +#ifndef MAPITAGS_H +#define MAPITAGS_H + +/* Determine if a property is transmittable. */ + +#define FIsTransmittable(ulPropTag) \ + ((PROP_ID (ulPropTag) < (ULONG)0x0E00) || \ + (PROP_ID (ulPropTag) >= (ULONG)0x8000) || \ + ((PROP_ID (ulPropTag) >= (ULONG)0x1000) && (PROP_ID (ulPropTag) < (ULONG)0x6000)) || \ + ((PROP_ID (ulPropTag) >= (ULONG)0x6800) && (PROP_ID (ulPropTag) < (ULONG)0x7C00))) + +/* + * Message envelope properties + */ + +#define PR_ACKNOWLEDGEMENT_MODE PROP_TAG( PT_LONG, 0x0001) +#define PR_ALTERNATE_RECIPIENT_ALLOWED PROP_TAG( PT_BOOLEAN, 0x0002) +#define PR_AUTHORIZING_USERS PROP_TAG( PT_BINARY, 0x0003) +#define PR_AUTO_FORWARD_COMMENT PROP_TAG( PT_TSTRING, 0x0004) +#define PR_AUTO_FORWARD_COMMENT_W PROP_TAG( PT_UNICODE, 0x0004) +#define PR_AUTO_FORWARD_COMMENT_A PROP_TAG( PT_STRING8, 0x0004) +#define PR_AUTO_FORWARDED PROP_TAG( PT_BOOLEAN, 0x0005) +#define PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID PROP_TAG( PT_BINARY, 0x0006) +#define PR_CONTENT_CORRELATOR PROP_TAG( PT_BINARY, 0x0007) +#define PR_CONTENT_IDENTIFIER PROP_TAG( PT_TSTRING, 0x0008) +#define PR_CONTENT_IDENTIFIER_W PROP_TAG( PT_UNICODE, 0x0008) +#define PR_CONTENT_IDENTIFIER_A PROP_TAG( PT_STRING8, 0x0008) +#define PR_CONTENT_LENGTH PROP_TAG( PT_LONG, 0x0009) +#define PR_CONTENT_RETURN_REQUESTED PROP_TAG( PT_BOOLEAN, 0x000A) + + + +#define PR_CONVERSATION_KEY PROP_TAG( PT_BINARY, 0x000B) + +#define PR_CONVERSION_EITS PROP_TAG( PT_BINARY, 0x000C) +#define PR_CONVERSION_WITH_LOSS_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x000D) +#define PR_CONVERTED_EITS PROP_TAG( PT_BINARY, 0x000E) +#define PR_DEFERRED_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x000F) +#define PR_DELIVER_TIME PROP_TAG( PT_SYSTIME, 0x0010) +#define PR_DISCARD_REASON PROP_TAG( PT_LONG, 0x0011) +#define PR_DISCLOSURE_OF_RECIPIENTS PROP_TAG( PT_BOOLEAN, 0x0012) +#define PR_DL_EXPANSION_HISTORY PROP_TAG( PT_BINARY, 0x0013) +#define PR_DL_EXPANSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0014) +#define PR_EXPIRY_TIME PROP_TAG( PT_SYSTIME, 0x0015) +#define PR_IMPLICIT_CONVERSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0016) +#define PR_IMPORTANCE PROP_TAG( PT_LONG, 0x0017) +#define PR_IPM_ID PROP_TAG( PT_BINARY, 0x0018) +#define PR_LATEST_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0019) +#define PR_MESSAGE_CLASS PROP_TAG( PT_TSTRING, 0x001A) +#define PR_MESSAGE_CLASS_W PROP_TAG( PT_UNICODE, 0x001A) +#define PR_MESSAGE_CLASS_A PROP_TAG( PT_STRING8, 0x001A) +#define PR_MESSAGE_DELIVERY_ID PROP_TAG( PT_BINARY, 0x001B) + + + + + +#define PR_MESSAGE_SECURITY_LABEL PROP_TAG( PT_BINARY, 0x001E) +#define PR_OBSOLETED_IPMS PROP_TAG( PT_BINARY, 0x001F) +#define PR_ORIGINALLY_INTENDED_RECIPIENT_NAME PROP_TAG( PT_BINARY, 0x0020) +#define PR_ORIGINAL_EITS PROP_TAG( PT_BINARY, 0x0021) +#define PR_ORIGINATOR_CERTIFICATE PROP_TAG( PT_BINARY, 0x0022) +#define PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0023) +#define PR_ORIGINATOR_RETURN_ADDRESS PROP_TAG( PT_BINARY, 0x0024) + + + +#define PR_PARENT_KEY PROP_TAG( PT_BINARY, 0x0025) +#define PR_PRIORITY PROP_TAG( PT_LONG, 0x0026) + + + +#define PR_ORIGIN_CHECK PROP_TAG( PT_BINARY, 0x0027) +#define PR_PROOF_OF_SUBMISSION_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0028) +#define PR_READ_RECEIPT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0029) +#define PR_RECEIPT_TIME PROP_TAG( PT_SYSTIME, 0x002A) +#define PR_RECIPIENT_REASSIGNMENT_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x002B) +#define PR_REDIRECTION_HISTORY PROP_TAG( PT_BINARY, 0x002C) +#define PR_RELATED_IPMS PROP_TAG( PT_BINARY, 0x002D) +#define PR_ORIGINAL_SENSITIVITY PROP_TAG( PT_LONG, 0x002E) +#define PR_LANGUAGES PROP_TAG( PT_TSTRING, 0x002F) +#define PR_LANGUAGES_W PROP_TAG( PT_UNICODE, 0x002F) +#define PR_LANGUAGES_A PROP_TAG( PT_STRING8, 0x002F) +#define PR_REPLY_TIME PROP_TAG( PT_SYSTIME, 0x0030) +#define PR_REPORT_TAG PROP_TAG( PT_BINARY, 0x0031) +#define PR_REPORT_TIME PROP_TAG( PT_SYSTIME, 0x0032) +#define PR_RETURNED_IPM PROP_TAG( PT_BOOLEAN, 0x0033) +#define PR_SECURITY PROP_TAG( PT_LONG, 0x0034) +#define PR_INCOMPLETE_COPY PROP_TAG( PT_BOOLEAN, 0x0035) +#define PR_SENSITIVITY PROP_TAG( PT_LONG, 0x0036) +#define PR_SUBJECT PROP_TAG( PT_TSTRING, 0x0037) +#define PR_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0037) +#define PR_SUBJECT_A PROP_TAG( PT_STRING8, 0x0037) +#define PR_SUBJECT_IPM PROP_TAG( PT_BINARY, 0x0038) +#define PR_CLIENT_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x0039) +#define PR_REPORT_NAME PROP_TAG( PT_TSTRING, 0x003A) +#define PR_REPORT_NAME_W PROP_TAG( PT_UNICODE, 0x003A) +#define PR_REPORT_NAME_A PROP_TAG( PT_STRING8, 0x003A) +#define PR_SENT_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x003B) +#define PR_X400_CONTENT_TYPE PROP_TAG( PT_BINARY, 0x003C) +#define PR_SUBJECT_PREFIX PROP_TAG( PT_TSTRING, 0x003D) +#define PR_SUBJECT_PREFIX_W PROP_TAG( PT_UNICODE, 0x003D) +#define PR_SUBJECT_PREFIX_A PROP_TAG( PT_STRING8, 0x003D) +#define PR_NON_RECEIPT_REASON PROP_TAG( PT_LONG, 0x003E) +#define PR_RECEIVED_BY_ENTRYID PROP_TAG( PT_BINARY, 0x003F) +#define PR_RECEIVED_BY_NAME PROP_TAG( PT_TSTRING, 0x0040) +#define PR_RECEIVED_BY_NAME_W PROP_TAG( PT_UNICODE, 0x0040) +#define PR_RECEIVED_BY_NAME_A PROP_TAG( PT_STRING8, 0x0040) +#define PR_SENT_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x0041) +#define PR_SENT_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x0042) +#define PR_SENT_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x0042) +#define PR_SENT_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x0042) +#define PR_RCVD_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x0043) +#define PR_RCVD_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x0044) +#define PR_RCVD_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x0044) +#define PR_RCVD_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x0044) +#define PR_REPORT_ENTRYID PROP_TAG( PT_BINARY, 0x0045) +#define PR_READ_RECEIPT_ENTRYID PROP_TAG( PT_BINARY, 0x0046) +#define PR_MESSAGE_SUBMISSION_ID PROP_TAG( PT_BINARY, 0x0047) +#define PR_PROVIDER_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x0048) +#define PR_ORIGINAL_SUBJECT PROP_TAG( PT_TSTRING, 0x0049) +#define PR_ORIGINAL_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0049) +#define PR_ORIGINAL_SUBJECT_A PROP_TAG( PT_STRING8, 0x0049) +#define PR_DISC_VAL PROP_TAG( PT_BOOLEAN, 0x004A) +#define PR_ORIG_MESSAGE_CLASS PROP_TAG( PT_TSTRING, 0x004B) +#define PR_ORIG_MESSAGE_CLASS_W PROP_TAG( PT_UNICODE, 0x004B) +#define PR_ORIG_MESSAGE_CLASS_A PROP_TAG( PT_STRING8, 0x004B) +#define PR_ORIGINAL_AUTHOR_ENTRYID PROP_TAG( PT_BINARY, 0x004C) +#define PR_ORIGINAL_AUTHOR_NAME PROP_TAG( PT_TSTRING, 0x004D) +#define PR_ORIGINAL_AUTHOR_NAME_W PROP_TAG( PT_UNICODE, 0x004D) +#define PR_ORIGINAL_AUTHOR_NAME_A PROP_TAG( PT_STRING8, 0x004D) +#define PR_ORIGINAL_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x004E) +#define PR_REPLY_RECIPIENT_ENTRIES PROP_TAG( PT_BINARY, 0x004F) +#define PR_REPLY_RECIPIENT_NAMES PROP_TAG( PT_TSTRING, 0x0050) +#define PR_REPLY_RECIPIENT_NAMES_W PROP_TAG( PT_UNICODE, 0x0050) +#define PR_REPLY_RECIPIENT_NAMES_A PROP_TAG( PT_STRING8, 0x0050) + +#define PR_RECEIVED_BY_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0051) +#define PR_RCVD_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0052) +#define PR_READ_RECEIPT_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0053) +#define PR_REPORT_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0054) +#define PR_ORIGINAL_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0055) +#define PR_ORIGINAL_AUTHOR_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0056) + +#define PR_MESSAGE_TO_ME PROP_TAG( PT_BOOLEAN, 0x0057) +#define PR_MESSAGE_CC_ME PROP_TAG( PT_BOOLEAN, 0x0058) +#define PR_MESSAGE_RECIP_ME PROP_TAG( PT_BOOLEAN, 0x0059) + +#define PR_ORIGINAL_SENDER_NAME PROP_TAG( PT_TSTRING, 0x005A) +#define PR_ORIGINAL_SENDER_NAME_W PROP_TAG( PT_UNICODE, 0x005A) +#define PR_ORIGINAL_SENDER_NAME_A PROP_TAG( PT_STRING8, 0x005A) +#define PR_ORIGINAL_SENDER_ENTRYID PROP_TAG( PT_BINARY, 0x005B) +#define PR_ORIGINAL_SENDER_SEARCH_KEY PROP_TAG( PT_BINARY, 0x005C) +#define PR_ORIGINAL_SENT_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x005D) +#define PR_ORIGINAL_SENT_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x005D) +#define PR_ORIGINAL_SENT_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x005D) +#define PR_ORIGINAL_SENT_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x005E) +#define PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x005F) + +#define PR_START_DATE PROP_TAG( PT_SYSTIME, 0x0060) +#define PR_END_DATE PROP_TAG( PT_SYSTIME, 0x0061) +#define PR_OWNER_APPT_ID PROP_TAG( PT_LONG, 0x0062) +#define PR_RESPONSE_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0063) + +#define PR_SENT_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0064) +#define PR_SENT_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0064) +#define PR_SENT_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0064) +#define PR_SENT_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0065) +#define PR_SENT_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0065) +#define PR_SENT_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0065) + +#define PR_ORIGINAL_SENDER_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0066) +#define PR_ORIGINAL_SENDER_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0066) +#define PR_ORIGINAL_SENDER_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0066) +#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0067) +#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0067) +#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0067) + +#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0068) +#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0068) +#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0068) +#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0069) +#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0069) +#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0069) + +#define PR_CONVERSATION_TOPIC PROP_TAG( PT_TSTRING, 0x0070) +#define PR_CONVERSATION_TOPIC_W PROP_TAG( PT_UNICODE, 0x0070) +#define PR_CONVERSATION_TOPIC_A PROP_TAG( PT_STRING8, 0x0070) +#define PR_CONVERSATION_INDEX PROP_TAG( PT_BINARY, 0x0071) + +#define PR_ORIGINAL_DISPLAY_BCC PROP_TAG( PT_TSTRING, 0x0072) +#define PR_ORIGINAL_DISPLAY_BCC_W PROP_TAG( PT_UNICODE, 0x0072) +#define PR_ORIGINAL_DISPLAY_BCC_A PROP_TAG( PT_STRING8, 0x0072) +#define PR_ORIGINAL_DISPLAY_CC PROP_TAG( PT_TSTRING, 0x0073) +#define PR_ORIGINAL_DISPLAY_CC_W PROP_TAG( PT_UNICODE, 0x0073) +#define PR_ORIGINAL_DISPLAY_CC_A PROP_TAG( PT_STRING8, 0x0073) +#define PR_ORIGINAL_DISPLAY_TO PROP_TAG( PT_TSTRING, 0x0074) +#define PR_ORIGINAL_DISPLAY_TO_W PROP_TAG( PT_UNICODE, 0x0074) +#define PR_ORIGINAL_DISPLAY_TO_A PROP_TAG( PT_STRING8, 0x0074) + +#define PR_RECEIVED_BY_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0075) +#define PR_RECEIVED_BY_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0075) +#define PR_RECEIVED_BY_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0075) +#define PR_RECEIVED_BY_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0076) +#define PR_RECEIVED_BY_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0076) +#define PR_RECEIVED_BY_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0076) + +#define PR_RCVD_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0077) +#define PR_RCVD_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0077) +#define PR_RCVD_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0077) +#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0078) +#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0078) +#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0078) + +#define PR_ORIGINAL_AUTHOR_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0079) +#define PR_ORIGINAL_AUTHOR_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0079) +#define PR_ORIGINAL_AUTHOR_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0079) +#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x007A) +#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x007A) +#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x007A) + +#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE PROP_TAG( PT_TSTRING, 0x007B) +#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x007B) +#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x007B) +#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x007C) +#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x007C) +#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x007C) + +#define PR_TRANSPORT_MESSAGE_HEADERS PROP_TAG(PT_TSTRING, 0x007D) +#define PR_TRANSPORT_MESSAGE_HEADERS_W PROP_TAG(PT_UNICODE, 0x007D) +#define PR_TRANSPORT_MESSAGE_HEADERS_A PROP_TAG(PT_STRING8, 0x007D) + +#define PR_DELEGATION PROP_TAG(PT_BINARY, 0x007E) + +#define PR_TNEF_CORRELATION_KEY PROP_TAG(PT_BINARY, 0x007F) + + + +/* + * Message content properties + */ + +#define PR_BODY PROP_TAG( PT_TSTRING, 0x1000) +#define PR_BODY_W PROP_TAG( PT_UNICODE, 0x1000) +#define PR_BODY_A PROP_TAG( PT_STRING8, 0x1000) +#define PR_REPORT_TEXT PROP_TAG( PT_TSTRING, 0x1001) +#define PR_REPORT_TEXT_W PROP_TAG( PT_UNICODE, 0x1001) +#define PR_REPORT_TEXT_A PROP_TAG( PT_STRING8, 0x1001) +#define PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY PROP_TAG( PT_BINARY, 0x1002) +#define PR_REPORTING_DL_NAME PROP_TAG( PT_BINARY, 0x1003) +#define PR_REPORTING_MTA_CERTIFICATE PROP_TAG( PT_BINARY, 0x1004) + +/* Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use PR_ORIGIN_CHECK */ + +#define PR_RTF_SYNC_BODY_CRC PROP_TAG( PT_LONG, 0x1006) +#define PR_RTF_SYNC_BODY_COUNT PROP_TAG( PT_LONG, 0x1007) +#define PR_RTF_SYNC_BODY_TAG PROP_TAG( PT_TSTRING, 0x1008) +#define PR_RTF_SYNC_BODY_TAG_W PROP_TAG( PT_UNICODE, 0x1008) +#define PR_RTF_SYNC_BODY_TAG_A PROP_TAG( PT_STRING8, 0x1008) +#define PR_RTF_COMPRESSED PROP_TAG( PT_BINARY, 0x1009) +#define PR_RTF_SYNC_PREFIX_COUNT PROP_TAG( PT_LONG, 0x1010) +#define PR_RTF_SYNC_TRAILING_COUNT PROP_TAG( PT_LONG, 0x1011) +#define PR_ORIGINALLY_INTENDED_RECIP_ENTRYID PROP_TAG( PT_BINARY, 0x1012) + +/* + * Reserved 0x1100-0x1200 + */ + + +/* + * Message recipient properties + */ + +#define PR_CONTENT_INTEGRITY_CHECK PROP_TAG( PT_BINARY, 0x0C00) +#define PR_EXPLICIT_CONVERSION PROP_TAG( PT_LONG, 0x0C01) +#define PR_IPM_RETURN_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C02) +#define PR_MESSAGE_TOKEN PROP_TAG( PT_BINARY, 0x0C03) +#define PR_NDR_REASON_CODE PROP_TAG( PT_LONG, 0x0C04) +#define PR_NDR_DIAG_CODE PROP_TAG( PT_LONG, 0x0C05) +#define PR_NON_RECEIPT_NOTIFICATION_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C06) +#define PR_DELIVERY_POINT PROP_TAG( PT_LONG, 0x0C07) + +#define PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C08) +#define PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT PROP_TAG( PT_BINARY, 0x0C09) +#define PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY PROP_TAG( PT_BOOLEAN, 0x0C0A) +#define PR_PHYSICAL_DELIVERY_MODE PROP_TAG( PT_LONG, 0x0C0B) +#define PR_PHYSICAL_DELIVERY_REPORT_REQUEST PROP_TAG( PT_LONG, 0x0C0C) +#define PR_PHYSICAL_FORWARDING_ADDRESS PROP_TAG( PT_BINARY, 0x0C0D) +#define PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C0E) +#define PR_PHYSICAL_FORWARDING_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0C0F) +#define PR_PHYSICAL_RENDITION_ATTRIBUTES PROP_TAG( PT_BINARY, 0x0C10) +#define PR_PROOF_OF_DELIVERY PROP_TAG( PT_BINARY, 0x0C11) +#define PR_PROOF_OF_DELIVERY_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C12) +#define PR_RECIPIENT_CERTIFICATE PROP_TAG( PT_BINARY, 0x0C13) +#define PR_RECIPIENT_NUMBER_FOR_ADVICE PROP_TAG( PT_TSTRING, 0x0C14) +#define PR_RECIPIENT_NUMBER_FOR_ADVICE_W PROP_TAG( PT_UNICODE, 0x0C14) +#define PR_RECIPIENT_NUMBER_FOR_ADVICE_A PROP_TAG( PT_STRING8, 0x0C14) +#define PR_RECIPIENT_TYPE PROP_TAG( PT_LONG, 0x0C15) +#define PR_REGISTERED_MAIL_TYPE PROP_TAG( PT_LONG, 0x0C16) +#define PR_REPLY_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C17) +#define PR_REQUESTED_DELIVERY_METHOD PROP_TAG( PT_LONG, 0x0C18) +#define PR_SENDER_ENTRYID PROP_TAG( PT_BINARY, 0x0C19) +#define PR_SENDER_NAME PROP_TAG( PT_TSTRING, 0x0C1A) +#define PR_SENDER_NAME_W PROP_TAG( PT_UNICODE, 0x0C1A) +#define PR_SENDER_NAME_A PROP_TAG( PT_STRING8, 0x0C1A) +#define PR_SUPPLEMENTARY_INFO PROP_TAG( PT_TSTRING, 0x0C1B) +#define PR_SUPPLEMENTARY_INFO_W PROP_TAG( PT_UNICODE, 0x0C1B) +#define PR_SUPPLEMENTARY_INFO_A PROP_TAG( PT_STRING8, 0x0C1B) +#define PR_TYPE_OF_MTS_USER PROP_TAG( PT_LONG, 0x0C1C) +#define PR_SENDER_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0C1D) +#define PR_SENDER_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0C1E) +#define PR_SENDER_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0C1E) +#define PR_SENDER_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0C1E) +#define PR_SENDER_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0C1F) +#define PR_SENDER_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0C1F) +#define PR_SENDER_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0C1F) + +/* + * Message non-transmittable properties + */ + +/* + * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS, + * are to be used in the exclude list passed to + * IMessage::CopyTo when the caller wants either the recipients or attachments + * of the message to not get copied. It is also used in the ProblemArray + * return from IMessage::CopyTo when an error is encountered copying them + */ + +#define PR_CURRENT_VERSION PROP_TAG( PT_I8, 0x0E00) +#define PR_DELETE_AFTER_SUBMIT PROP_TAG( PT_BOOLEAN, 0x0E01) +#define PR_DISPLAY_BCC PROP_TAG( PT_TSTRING, 0x0E02) +#define PR_DISPLAY_BCC_W PROP_TAG( PT_UNICODE, 0x0E02) +#define PR_DISPLAY_BCC_A PROP_TAG( PT_STRING8, 0x0E02) +#define PR_DISPLAY_CC PROP_TAG( PT_TSTRING, 0x0E03) +#define PR_DISPLAY_CC_W PROP_TAG( PT_UNICODE, 0x0E03) +#define PR_DISPLAY_CC_A PROP_TAG( PT_STRING8, 0x0E03) +#define PR_DISPLAY_TO PROP_TAG( PT_TSTRING, 0x0E04) +#define PR_DISPLAY_TO_W PROP_TAG( PT_UNICODE, 0x0E04) +#define PR_DISPLAY_TO_A PROP_TAG( PT_STRING8, 0x0E04) +#define PR_PARENT_DISPLAY PROP_TAG( PT_TSTRING, 0x0E05) +#define PR_PARENT_DISPLAY_W PROP_TAG( PT_UNICODE, 0x0E05) +#define PR_PARENT_DISPLAY_A PROP_TAG( PT_STRING8, 0x0E05) +#define PR_MESSAGE_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0E06) +#define PR_MESSAGE_FLAGS PROP_TAG( PT_LONG, 0x0E07) +#define PR_MESSAGE_SIZE PROP_TAG( PT_LONG, 0x0E08) +#define PR_PARENT_ENTRYID PROP_TAG( PT_BINARY, 0x0E09) +#define PR_SENTMAIL_ENTRYID PROP_TAG( PT_BINARY, 0x0E0A) +#define PR_CORRELATE PROP_TAG( PT_BOOLEAN, 0x0E0C) +#define PR_CORRELATE_MTSID PROP_TAG( PT_BINARY, 0x0E0D) +#define PR_DISCRETE_VALUES PROP_TAG( PT_BOOLEAN, 0x0E0E) +#define PR_RESPONSIBILITY PROP_TAG( PT_BOOLEAN, 0x0E0F) +#define PR_SPOOLER_STATUS PROP_TAG( PT_LONG, 0x0E10) +#define PR_TRANSPORT_STATUS PROP_TAG( PT_LONG, 0x0E11) +#define PR_MESSAGE_RECIPIENTS PROP_TAG( PT_OBJECT, 0x0E12) +#define PR_MESSAGE_ATTACHMENTS PROP_TAG( PT_OBJECT, 0x0E13) +#define PR_SUBMIT_FLAGS PROP_TAG( PT_LONG, 0x0E14) +#define PR_RECIPIENT_STATUS PROP_TAG( PT_LONG, 0x0E15) +#define PR_TRANSPORT_KEY PROP_TAG( PT_LONG, 0x0E16) +#define PR_MSG_STATUS PROP_TAG( PT_LONG, 0x0E17) +#define PR_MESSAGE_DOWNLOAD_TIME PROP_TAG( PT_LONG, 0x0E18) +#define PR_CREATION_VERSION PROP_TAG( PT_I8, 0x0E19) +#define PR_MODIFY_VERSION PROP_TAG( PT_I8, 0x0E1A) +#define PR_HASATTACH PROP_TAG( PT_BOOLEAN, 0x0E1B) +#define PR_BODY_CRC PROP_TAG( PT_LONG, 0x0E1C) +#define PR_NORMALIZED_SUBJECT PROP_TAG( PT_TSTRING, 0x0E1D) +#define PR_NORMALIZED_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0E1D) +#define PR_NORMALIZED_SUBJECT_A PROP_TAG( PT_STRING8, 0x0E1D) +#define PR_RTF_IN_SYNC PROP_TAG( PT_BOOLEAN, 0x0E1F) +#define PR_ATTACH_SIZE PROP_TAG( PT_LONG, 0x0E20) +#define PR_ATTACH_NUM PROP_TAG( PT_LONG, 0x0E21) +#define PR_PREPROCESS PROP_TAG( PT_BOOLEAN, 0x0E22) + +/* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 */ + +#define PR_ORIGINATING_MTA_CERTIFICATE PROP_TAG( PT_BINARY, 0x0E25) +#define PR_PROOF_OF_SUBMISSION PROP_TAG( PT_BINARY, 0x0E26) + + +/* + * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) is + * further broken down into ranges to make assigning new property IDs easier. + * + * From To Kind of property + * -------------------------------- + * 3000 32FF MAPI_defined common property + * 3200 33FF MAPI_defined form property + * 3400 35FF MAPI_defined message store property + * 3600 36FF MAPI_defined Folder or AB Container property + * 3700 38FF MAPI_defined attachment property + * 3900 39FF MAPI_defined address book property + * 3A00 3BFF MAPI_defined mailuser property + * 3C00 3CFF MAPI_defined DistList property + * 3D00 3DFF MAPI_defined Profile Section property + * 3E00 3EFF MAPI_defined Status property + * 3F00 3FFF MAPI_defined display table property + */ + +/* + * Properties common to numerous MAPI objects. + * + * Those properties that can appear on messages are in the + * non-transmittable range for messages. They start at the high + * end of that range and work down. + * + * Properties that never appear on messages are defined in the common + * property range (see above). + */ + +/* + * properties that are common to multiple objects (including message objects) + * -- these ids are in the non-transmittable range + */ + +#define PR_ENTRYID PROP_TAG( PT_BINARY, 0x0FFF) +#define PR_OBJECT_TYPE PROP_TAG( PT_LONG, 0x0FFE) +#define PR_ICON PROP_TAG( PT_BINARY, 0x0FFD) +#define PR_MINI_ICON PROP_TAG( PT_BINARY, 0x0FFC) +#define PR_STORE_ENTRYID PROP_TAG( PT_BINARY, 0x0FFB) +#define PR_STORE_RECORD_KEY PROP_TAG( PT_BINARY, 0x0FFA) +#define PR_RECORD_KEY PROP_TAG( PT_BINARY, 0x0FF9) +#define PR_MAPPING_SIGNATURE PROP_TAG( PT_BINARY, 0x0FF8) +#define PR_ACCESS_LEVEL PROP_TAG( PT_LONG, 0x0FF7) +#define PR_INSTANCE_KEY PROP_TAG( PT_BINARY, 0x0FF6) +#define PR_ROW_TYPE PROP_TAG( PT_LONG, 0x0FF5) +#define PR_ACCESS PROP_TAG( PT_LONG, 0x0FF4) + +/* + * properties that are common to multiple objects (usually not including message objects) + * -- these ids are in the transmittable range + */ + +#define PR_ROWID PROP_TAG( PT_LONG, 0x3000) +#define PR_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3001) +#define PR_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3001) +#define PR_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3001) +#define PR_ADDRTYPE PROP_TAG( PT_TSTRING, 0x3002) +#define PR_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x3002) +#define PR_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x3002) +#define PR_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x3003) +#define PR_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3003) +#define PR_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x3003) +#define PR_COMMENT PROP_TAG( PT_TSTRING, 0x3004) +#define PR_COMMENT_W PROP_TAG( PT_UNICODE, 0x3004) +#define PR_COMMENT_A PROP_TAG( PT_STRING8, 0x3004) +#define PR_DEPTH PROP_TAG( PT_LONG, 0x3005) +#define PR_PROVIDER_DISPLAY PROP_TAG( PT_TSTRING, 0x3006) +#define PR_PROVIDER_DISPLAY_W PROP_TAG( PT_UNICODE, 0x3006) +#define PR_PROVIDER_DISPLAY_A PROP_TAG( PT_STRING8, 0x3006) +#define PR_CREATION_TIME PROP_TAG( PT_SYSTIME, 0x3007) +#define PR_LAST_MODIFICATION_TIME PROP_TAG( PT_SYSTIME, 0x3008) +#define PR_RESOURCE_FLAGS PROP_TAG( PT_LONG, 0x3009) +#define PR_PROVIDER_DLL_NAME PROP_TAG( PT_TSTRING, 0x300A) +#define PR_PROVIDER_DLL_NAME_W PROP_TAG( PT_UNICODE, 0x300A) +#define PR_PROVIDER_DLL_NAME_A PROP_TAG( PT_STRING8, 0x300A) +#define PR_SEARCH_KEY PROP_TAG( PT_BINARY, 0x300B) +#define PR_PROVIDER_UID PROP_TAG( PT_BINARY, 0x300C) +#define PR_PROVIDER_ORDINAL PROP_TAG( PT_LONG, 0x300D) + +/* + * MAPI Form properties + */ +#define PR_FORM_VERSION PROP_TAG(PT_TSTRING, 0x3301) +#define PR_FORM_VERSION_W PROP_TAG(PT_UNICODE, 0x3301) +#define PR_FORM_VERSION_A PROP_TAG(PT_STRING8, 0x3301) +#define PR_FORM_CLSID PROP_TAG(PT_CLSID, 0x3302) +#define PR_FORM_CONTACT_NAME PROP_TAG(PT_TSTRING, 0x3303) +#define PR_FORM_CONTACT_NAME_W PROP_TAG(PT_UNICODE, 0x3303) +#define PR_FORM_CONTACT_NAME_A PROP_TAG(PT_STRING8, 0x3303) +#define PR_FORM_CATEGORY PROP_TAG(PT_TSTRING, 0x3304) +#define PR_FORM_CATEGORY_W PROP_TAG(PT_UNICODE, 0x3304) +#define PR_FORM_CATEGORY_A PROP_TAG(PT_STRING8, 0x3304) +#define PR_FORM_CATEGORY_SUB PROP_TAG(PT_TSTRING, 0x3305) +#define PR_FORM_CATEGORY_SUB_W PROP_TAG(PT_UNICODE, 0x3305) +#define PR_FORM_CATEGORY_SUB_A PROP_TAG(PT_STRING8, 0x3305) +#define PR_FORM_HOST_MAP PROP_TAG(PT_MV_LONG, 0x3306) +#define PR_FORM_HIDDEN PROP_TAG(PT_BOOLEAN, 0x3307) +#define PR_FORM_DESIGNER_NAME PROP_TAG(PT_TSTRING, 0x3308) +#define PR_FORM_DESIGNER_NAME_W PROP_TAG(PT_UNICODE, 0x3308) +#define PR_FORM_DESIGNER_NAME_A PROP_TAG(PT_STRING8, 0x3308) +#define PR_FORM_DESIGNER_GUID PROP_TAG(PT_CLSID, 0x3309) +#define PR_FORM_MESSAGE_BEHAVIOR PROP_TAG(PT_LONG, 0x330A) + +/* + * Message store properties + */ + +#define PR_DEFAULT_STORE PROP_TAG( PT_BOOLEAN, 0x3400) +#define PR_STORE_SUPPORT_MASK PROP_TAG( PT_LONG, 0x340D) +#define PR_STORE_STATE PROP_TAG( PT_LONG, 0x340E) + +#define PR_IPM_SUBTREE_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3410) +#define PR_IPM_OUTBOX_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3411) +#define PR_IPM_WASTEBASKET_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3412) +#define PR_IPM_SENTMAIL_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3413) +#define PR_MDB_PROVIDER PROP_TAG( PT_BINARY, 0x3414) +#define PR_RECEIVE_FOLDER_SETTINGS PROP_TAG( PT_OBJECT, 0x3415) + +#define PR_VALID_FOLDER_MASK PROP_TAG( PT_LONG, 0x35DF) +#define PR_IPM_SUBTREE_ENTRYID PROP_TAG( PT_BINARY, 0x35E0) + +#define PR_IPM_OUTBOX_ENTRYID PROP_TAG( PT_BINARY, 0x35E2) +#define PR_IPM_WASTEBASKET_ENTRYID PROP_TAG( PT_BINARY, 0x35E3) +#define PR_IPM_SENTMAIL_ENTRYID PROP_TAG( PT_BINARY, 0x35E4) +#define PR_VIEWS_ENTRYID PROP_TAG( PT_BINARY, 0x35E5) +#define PR_COMMON_VIEWS_ENTRYID PROP_TAG( PT_BINARY, 0x35E6) +#define PR_FINDER_ENTRYID PROP_TAG( PT_BINARY, 0x35E7) + +/* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by PR_VALID_FOLDER_MASK */ + + +/* + * Folder and AB Container properties + */ + +#define PR_CONTAINER_FLAGS PROP_TAG( PT_LONG, 0x3600) +#define PR_FOLDER_TYPE PROP_TAG( PT_LONG, 0x3601) +#define PR_CONTENT_COUNT PROP_TAG( PT_LONG, 0x3602) +#define PR_CONTENT_UNREAD PROP_TAG( PT_LONG, 0x3603) +#define PR_CREATE_TEMPLATES PROP_TAG( PT_OBJECT, 0x3604) +#define PR_DETAILS_TABLE PROP_TAG( PT_OBJECT, 0x3605) +#define PR_SEARCH PROP_TAG( PT_OBJECT, 0x3607) +#define PR_SELECTABLE PROP_TAG( PT_BOOLEAN, 0x3609) +#define PR_SUBFOLDERS PROP_TAG( PT_BOOLEAN, 0x360A) +#define PR_STATUS PROP_TAG( PT_LONG, 0x360B) +#define PR_ANR PROP_TAG( PT_TSTRING, 0x360C) +#define PR_ANR_W PROP_TAG( PT_UNICODE, 0x360C) +#define PR_ANR_A PROP_TAG( PT_STRING8, 0x360C) +#define PR_CONTENTS_SORT_ORDER PROP_TAG( PT_MV_LONG, 0x360D) +#define PR_CONTAINER_HIERARCHY PROP_TAG( PT_OBJECT, 0x360E) +#define PR_CONTAINER_CONTENTS PROP_TAG( PT_OBJECT, 0x360F) +#define PR_FOLDER_ASSOCIATED_CONTENTS PROP_TAG( PT_OBJECT, 0x3610) +#define PR_DEF_CREATE_DL PROP_TAG( PT_BINARY, 0x3611) +#define PR_DEF_CREATE_MAILUSER PROP_TAG( PT_BINARY, 0x3612) +#define PR_CONTAINER_CLASS PROP_TAG( PT_TSTRING, 0x3613) +#define PR_CONTAINER_CLASS_W PROP_TAG( PT_UNICODE, 0x3613) +#define PR_CONTAINER_CLASS_A PROP_TAG( PT_STRING8, 0x3613) +#define PR_CONTAINER_MODIFY_VERSION PROP_TAG( PT_I8, 0x3614) +#define PR_AB_PROVIDER_ID PROP_TAG( PT_BINARY, 0x3615) +#define PR_DEFAULT_VIEW_ENTRYID PROP_TAG( PT_BINARY, 0x3616) +#define PR_ASSOC_CONTENT_COUNT PROP_TAG( PT_LONG, 0x3617) + +/* Reserved 0x36C0-0x36FF */ + +/* + * Attachment properties + */ + +#define PR_ATTACHMENT_X400_PARAMETERS PROP_TAG( PT_BINARY, 0x3700) +#define PR_ATTACH_DATA_OBJ PROP_TAG( PT_OBJECT, 0x3701) +#define PR_ATTACH_DATA_BIN PROP_TAG( PT_BINARY, 0x3701) +#define PR_ATTACH_ENCODING PROP_TAG( PT_BINARY, 0x3702) +#define PR_ATTACH_EXTENSION PROP_TAG( PT_TSTRING, 0x3703) +#define PR_ATTACH_EXTENSION_W PROP_TAG( PT_UNICODE, 0x3703) +#define PR_ATTACH_EXTENSION_A PROP_TAG( PT_STRING8, 0x3703) +#define PR_ATTACH_FILENAME PROP_TAG( PT_TSTRING, 0x3704) +#define PR_ATTACH_FILENAME_W PROP_TAG( PT_UNICODE, 0x3704) +#define PR_ATTACH_FILENAME_A PROP_TAG( PT_STRING8, 0x3704) +#define PR_ATTACH_METHOD PROP_TAG( PT_LONG, 0x3705) +#define PR_ATTACH_LONG_FILENAME PROP_TAG( PT_TSTRING, 0x3707) +#define PR_ATTACH_LONG_FILENAME_W PROP_TAG( PT_UNICODE, 0x3707) +#define PR_ATTACH_LONG_FILENAME_A PROP_TAG( PT_STRING8, 0x3707) +#define PR_ATTACH_PATHNAME PROP_TAG( PT_TSTRING, 0x3708) +#define PR_ATTACH_PATHNAME_W PROP_TAG( PT_UNICODE, 0x3708) +#define PR_ATTACH_PATHNAME_A PROP_TAG( PT_STRING8, 0x3708) +#define PR_ATTACH_RENDERING PROP_TAG( PT_BINARY, 0x3709) +#define PR_ATTACH_TAG PROP_TAG( PT_BINARY, 0x370A) +#define PR_RENDERING_POSITION PROP_TAG( PT_LONG, 0x370B) +#define PR_ATTACH_TRANSPORT_NAME PROP_TAG( PT_TSTRING, 0x370C) +#define PR_ATTACH_TRANSPORT_NAME_W PROP_TAG( PT_UNICODE, 0x370C) +#define PR_ATTACH_TRANSPORT_NAME_A PROP_TAG( PT_STRING8, 0x370C) +#define PR_ATTACH_LONG_PATHNAME PROP_TAG( PT_TSTRING, 0x370D) +#define PR_ATTACH_LONG_PATHNAME_W PROP_TAG( PT_UNICODE, 0x370D) +#define PR_ATTACH_LONG_PATHNAME_A PROP_TAG( PT_STRING8, 0x370D) +#define PR_ATTACH_MIME_TAG PROP_TAG( PT_TSTRING, 0x370E) +#define PR_ATTACH_MIME_TAG_W PROP_TAG( PT_UNICODE, 0x370E) +#define PR_ATTACH_MIME_TAG_A PROP_TAG( PT_STRING8, 0x370E) +#define PR_ATTACH_ADDITIONAL_INFO PROP_TAG( PT_BINARY, 0x370F) + +/* + * AB Object properties + */ + +#define PR_DISPLAY_TYPE PROP_TAG( PT_LONG, 0x3900) +#define PR_TEMPLATEID PROP_TAG( PT_BINARY, 0x3902) +#define PR_PRIMARY_CAPABILITY PROP_TAG( PT_BINARY, 0x3904) + + +/* + * Mail user properties + */ +#define PR_7BIT_DISPLAY_NAME PROP_TAG( PT_STRING8, 0x39FF) +#define PR_ACCOUNT PROP_TAG( PT_TSTRING, 0x3A00) +#define PR_ACCOUNT_W PROP_TAG( PT_UNICODE, 0x3A00) +#define PR_ACCOUNT_A PROP_TAG( PT_STRING8, 0x3A00) +#define PR_ALTERNATE_RECIPIENT PROP_TAG( PT_BINARY, 0x3A01) +#define PR_CALLBACK_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A02) +#define PR_CALLBACK_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A02) +#define PR_CALLBACK_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A02) +#define PR_CONVERSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x3A03) +#define PR_DISCLOSE_RECIPIENTS PROP_TAG( PT_BOOLEAN, 0x3A04) +#define PR_GENERATION PROP_TAG( PT_TSTRING, 0x3A05) +#define PR_GENERATION_W PROP_TAG( PT_UNICODE, 0x3A05) +#define PR_GENERATION_A PROP_TAG( PT_STRING8, 0x3A05) +#define PR_GIVEN_NAME PROP_TAG( PT_TSTRING, 0x3A06) +#define PR_GIVEN_NAME_W PROP_TAG( PT_UNICODE, 0x3A06) +#define PR_GIVEN_NAME_A PROP_TAG( PT_STRING8, 0x3A06) +#define PR_GOVERNMENT_ID_NUMBER PROP_TAG( PT_TSTRING, 0x3A07) +#define PR_GOVERNMENT_ID_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A07) +#define PR_GOVERNMENT_ID_NUMBER_A PROP_TAG( PT_STRING8, 0x3A07) +#define PR_BUSINESS_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A08) +#define PR_BUSINESS_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A08) +#define PR_BUSINESS_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A08) +#define PR_OFFICE_TELEPHONE_NUMBER PR_BUSINESS_TELEPHONE_NUMBER +#define PR_OFFICE_TELEPHONE_NUMBER_W PR_BUSINESS_TELEPHONE_NUMBER_W +#define PR_OFFICE_TELEPHONE_NUMBER_A PR_BUSINESS_TELEPHONE_NUMBER_A +#define PR_HOME_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A09) +#define PR_HOME_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A09) +#define PR_HOME_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A09) +#define PR_INITIALS PROP_TAG( PT_TSTRING, 0x3A0A) +#define PR_INITIALS_W PROP_TAG( PT_UNICODE, 0x3A0A) +#define PR_INITIALS_A PROP_TAG( PT_STRING8, 0x3A0A) +#define PR_KEYWORD PROP_TAG( PT_TSTRING, 0x3A0B) +#define PR_KEYWORD_W PROP_TAG( PT_UNICODE, 0x3A0B) +#define PR_KEYWORD_A PROP_TAG( PT_STRING8, 0x3A0B) +#define PR_LANGUAGE PROP_TAG( PT_TSTRING, 0x3A0C) +#define PR_LANGUAGE_W PROP_TAG( PT_UNICODE, 0x3A0C) +#define PR_LANGUAGE_A PROP_TAG( PT_STRING8, 0x3A0C) +#define PR_LOCATION PROP_TAG( PT_TSTRING, 0x3A0D) +#define PR_LOCATION_W PROP_TAG( PT_UNICODE, 0x3A0D) +#define PR_LOCATION_A PROP_TAG( PT_STRING8, 0x3A0D) +#define PR_MAIL_PERMISSION PROP_TAG( PT_BOOLEAN, 0x3A0E) +#define PR_MHS_COMMON_NAME PROP_TAG( PT_TSTRING, 0x3A0F) +#define PR_MHS_COMMON_NAME_W PROP_TAG( PT_UNICODE, 0x3A0F) +#define PR_MHS_COMMON_NAME_A PROP_TAG( PT_STRING8, 0x3A0F) +#define PR_ORGANIZATIONAL_ID_NUMBER PROP_TAG( PT_TSTRING, 0x3A10) +#define PR_ORGANIZATIONAL_ID_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A10) +#define PR_ORGANIZATIONAL_ID_NUMBER_A PROP_TAG( PT_STRING8, 0x3A10) +#define PR_SURNAME PROP_TAG( PT_TSTRING, 0x3A11) +#define PR_SURNAME_W PROP_TAG( PT_UNICODE, 0x3A11) +#define PR_SURNAME_A PROP_TAG( PT_STRING8, 0x3A11) +#define PR_ORIGINAL_ENTRYID PROP_TAG( PT_BINARY, 0x3A12) +#define PR_ORIGINAL_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3A13) +#define PR_ORIGINAL_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3A13) +#define PR_ORIGINAL_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3A13) +#define PR_ORIGINAL_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3A14) +#define PR_POSTAL_ADDRESS PROP_TAG( PT_TSTRING, 0x3A15) +#define PR_POSTAL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3A15) +#define PR_POSTAL_ADDRESS_A PROP_TAG( PT_STRING8, 0x3A15) +#define PR_COMPANY_NAME PROP_TAG( PT_TSTRING, 0x3A16) +#define PR_COMPANY_NAME_W PROP_TAG( PT_UNICODE, 0x3A16) +#define PR_COMPANY_NAME_A PROP_TAG( PT_STRING8, 0x3A16) +#define PR_TITLE PROP_TAG( PT_TSTRING, 0x3A17) +#define PR_TITLE_W PROP_TAG( PT_UNICODE, 0x3A17) +#define PR_TITLE_A PROP_TAG( PT_STRING8, 0x3A17) +#define PR_DEPARTMENT_NAME PROP_TAG( PT_TSTRING, 0x3A18) +#define PR_DEPARTMENT_NAME_W PROP_TAG( PT_UNICODE, 0x3A18) +#define PR_DEPARTMENT_NAME_A PROP_TAG( PT_STRING8, 0x3A18) +#define PR_OFFICE_LOCATION PROP_TAG( PT_TSTRING, 0x3A19) +#define PR_OFFICE_LOCATION_W PROP_TAG( PT_UNICODE, 0x3A19) +#define PR_OFFICE_LOCATION_A PROP_TAG( PT_STRING8, 0x3A19) +#define PR_PRIMARY_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1A) +#define PR_PRIMARY_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1A) +#define PR_PRIMARY_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1A) +#define PR_BUSINESS2_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1B) +#define PR_BUSINESS2_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1B) +#define PR_BUSINESS2_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1B) +#define PR_OFFICE2_TELEPHONE_NUMBER PR_BUSINESS2_TELEPHONE_NUMBER +#define PR_OFFICE2_TELEPHONE_NUMBER_W PR_BUSINESS2_TELEPHONE_NUMBER_W +#define PR_OFFICE2_TELEPHONE_NUMBER_A PR_BUSINESS2_TELEPHONE_NUMBER_A +#define PR_MOBILE_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1C) +#define PR_MOBILE_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1C) +#define PR_MOBILE_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1C) +#define PR_CELLULAR_TELEPHONE_NUMBER PR_MOBILE_TELEPHONE_NUMBER +#define PR_CELLULAR_TELEPHONE_NUMBER_W PR_MOBILE_TELEPHONE_NUMBER_W +#define PR_CELLULAR_TELEPHONE_NUMBER_A PR_MOBILE_TELEPHONE_NUMBER_A +#define PR_RADIO_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1D) +#define PR_RADIO_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1D) +#define PR_RADIO_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1D) +#define PR_CAR_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1E) +#define PR_CAR_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1E) +#define PR_CAR_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1E) +#define PR_OTHER_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1F) +#define PR_OTHER_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1F) +#define PR_OTHER_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1F) +#define PR_TRANSMITABLE_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3A20) +#define PR_TRANSMITABLE_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3A20) +#define PR_TRANSMITABLE_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3A20) +#define PR_PAGER_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A21) +#define PR_PAGER_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A21) +#define PR_PAGER_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A21) +#define PR_BEEPER_TELEPHONE_NUMBER PR_PAGER_TELEPHONE_NUMBER +#define PR_BEEPER_TELEPHONE_NUMBER_W PR_PAGER_TELEPHONE_NUMBER_W +#define PR_BEEPER_TELEPHONE_NUMBER_A PR_PAGER_TELEPHONE_NUMBER_A +#define PR_USER_CERTIFICATE PROP_TAG( PT_BINARY, 0x3A22) +#define PR_PRIMARY_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A23) +#define PR_PRIMARY_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A23) +#define PR_PRIMARY_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A23) +#define PR_BUSINESS_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A24) +#define PR_BUSINESS_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A24) +#define PR_BUSINESS_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A24) +#define PR_HOME_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A25) +#define PR_HOME_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A25) +#define PR_HOME_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A25) +#define PR_COUNTRY PROP_TAG( PT_TSTRING, 0x3A26) +#define PR_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A26) +#define PR_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A26) +#define PR_BUSINESS_ADDRESS_COUNTRY PR_COUNTRY +#define PR_BUSINESS_ADDRESS_COUNTRY_W PR_COUNTRY_W +#define PR_BUSINESS_ADDRESS_COUNTRY_A PR_COUNTRY_A + +#define PR_LOCALITY PROP_TAG( PT_TSTRING, 0x3A27) +#define PR_LOCALITY_W PROP_TAG( PT_UNICODE, 0x3A27) +#define PR_LOCALITY_A PROP_TAG( PT_STRING8, 0x3A27) +#define PR_BUSINESS_ADDRESS_CITY PR_LOCALITY +#define PR_BUSINESS_ADDRESS_CITY_W PR_LOCALITY_W +#define PR_BUSINESS_ADDRESS_CITY_A PR_LOCALITY_A + +#define PR_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A28) +#define PR_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A28) +#define PR_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A28) +#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE PR_STATE_OR_PROVINCE +#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W PR_STATE_OR_PROVINCE_W +#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A PR_STATE_OR_PROVINCE_A + +#define PR_STREET_ADDRESS PROP_TAG( PT_TSTRING, 0x3A29) +#define PR_STREET_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3A29) +#define PR_STREET_ADDRESS_A PROP_TAG( PT_STRING8, 0x3A29) +#define PR_BUSINESS_ADDRESS_STREET PR_STREET_ADDRESS +#define PR_BUSINESS_ADDRESS_STREET_W PR_STREET_ADDRESS_W +#define PR_BUSINESS_ADDRESS_STREET_A PR_STREET_ADDRESS_A + +#define PR_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A2A) +#define PR_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A2A) +#define PR_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A2A) +#define PR_BUSINESS_ADDRESS_POSTAL_CODE PR_POSTAL_CODE +#define PR_BUSINESS_ADDRESS_POSTAL_CODE_W PR_POSTAL_CODE_W +#define PR_BUSINESS_ADDRESS_POSTAL_CODE_A PR_POSTAL_CODE_A + + +#define PR_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A2B) +#define PR_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A2B) +#define PR_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A2B) +#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX PR_POST_OFFICE_BOX +#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_W PR_POST_OFFICE_BOX_W +#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_A PR_POST_OFFICE_BOX_A + + +#define PR_TELEX_NUMBER PROP_TAG( PT_TSTRING, 0x3A2C) +#define PR_TELEX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2C) +#define PR_TELEX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2C) +#define PR_ISDN_NUMBER PROP_TAG( PT_TSTRING, 0x3A2D) +#define PR_ISDN_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2D) +#define PR_ISDN_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2D) +#define PR_ASSISTANT_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A2E) +#define PR_ASSISTANT_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2E) +#define PR_ASSISTANT_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2E) +#define PR_HOME2_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A2F) +#define PR_HOME2_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2F) +#define PR_HOME2_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2F) +#define PR_ASSISTANT PROP_TAG( PT_TSTRING, 0x3A30) +#define PR_ASSISTANT_W PROP_TAG( PT_UNICODE, 0x3A30) +#define PR_ASSISTANT_A PROP_TAG( PT_STRING8, 0x3A30) +#define PR_SEND_RICH_INFO PROP_TAG( PT_BOOLEAN, 0x3A40) + +#define PR_WEDDING_ANNIVERSARY PROP_TAG( PT_SYSTIME, 0x3A41) +#define PR_BIRTHDAY PROP_TAG( PT_SYSTIME, 0x3A42) + + +#define PR_HOBBIES PROP_TAG( PT_TSTRING, 0x3A43) +#define PR_HOBBIES_W PROP_TAG( PT_UNICODE, 0x3A43) +#define PR_HOBBIES_A PROP_TAG( PT_STRING8, 0x3A43) + +#define PR_MIDDLE_NAME PROP_TAG( PT_TSTRING, 0x3A44) +#define PR_MIDDLE_NAME_W PROP_TAG( PT_UNICODE, 0x3A44) +#define PR_MIDDLE_NAME_A PROP_TAG( PT_STRING8, 0x3A44) + +#define PR_DISPLAY_NAME_PREFIX PROP_TAG( PT_TSTRING, 0x3A45) +#define PR_DISPLAY_NAME_PREFIX_W PROP_TAG( PT_UNICODE, 0x3A45) +#define PR_DISPLAY_NAME_PREFIX_A PROP_TAG( PT_STRING8, 0x3A45) + +#define PR_PROFESSION PROP_TAG( PT_TSTRING, 0x3A46) +#define PR_PROFESSION_W PROP_TAG( PT_UNICODE, 0x3A46) +#define PR_PROFESSION_A PROP_TAG( PT_STRING8, 0x3A46) + +#define PR_PREFERRED_BY_NAME PROP_TAG( PT_TSTRING, 0x3A47) +#define PR_PREFERRED_BY_NAME_W PROP_TAG( PT_UNICODE, 0x3A47) +#define PR_PREFERRED_BY_NAME_A PROP_TAG( PT_STRING8, 0x3A47) + +#define PR_SPOUSE_NAME PROP_TAG( PT_TSTRING, 0x3A48) +#define PR_SPOUSE_NAME_W PROP_TAG( PT_UNICODE, 0x3A48) +#define PR_SPOUSE_NAME_A PROP_TAG( PT_STRING8, 0x3A48) + +#define PR_COMPUTER_NETWORK_NAME PROP_TAG( PT_TSTRING, 0x3A49) +#define PR_COMPUTER_NETWORK_NAME_W PROP_TAG( PT_UNICODE, 0x3A49) +#define PR_COMPUTER_NETWORK_NAME_A PROP_TAG( PT_STRING8, 0x3A49) + +#define PR_CUSTOMER_ID PROP_TAG( PT_TSTRING, 0x3A4A) +#define PR_CUSTOMER_ID_W PROP_TAG( PT_UNICODE, 0x3A4A) +#define PR_CUSTOMER_ID_A PROP_TAG( PT_STRING8, 0x3A4A) + +#define PR_TTYTDD_PHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A4B) +#define PR_TTYTDD_PHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A4B) +#define PR_TTYTDD_PHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A4B) + +#define PR_FTP_SITE PROP_TAG( PT_TSTRING, 0x3A4C) +#define PR_FTP_SITE_W PROP_TAG( PT_UNICODE, 0x3A4C) +#define PR_FTP_SITE_A PROP_TAG( PT_STRING8, 0x3A4C) + +#define PR_GENDER PROP_TAG( PT_SHORT, 0x3A4D) + +#define PR_MANAGER_NAME PROP_TAG( PT_TSTRING, 0x3A4E) +#define PR_MANAGER_NAME_W PROP_TAG( PT_UNICODE, 0x3A4E) +#define PR_MANAGER_NAME_A PROP_TAG( PT_STRING8, 0x3A4E) + +#define PR_NICKNAME PROP_TAG( PT_TSTRING, 0x3A4F) +#define PR_NICKNAME_W PROP_TAG( PT_UNICODE, 0x3A4F) +#define PR_NICKNAME_A PROP_TAG( PT_STRING8, 0x3A4F) + +#define PR_PERSONAL_HOME_PAGE PROP_TAG( PT_TSTRING, 0x3A50) +#define PR_PERSONAL_HOME_PAGE_W PROP_TAG( PT_UNICODE, 0x3A50) +#define PR_PERSONAL_HOME_PAGE_A PROP_TAG( PT_STRING8, 0x3A50) + + +#define PR_BUSINESS_HOME_PAGE PROP_TAG( PT_TSTRING, 0x3A51) +#define PR_BUSINESS_HOME_PAGE_W PROP_TAG( PT_UNICODE, 0x3A51) +#define PR_BUSINESS_HOME_PAGE_A PROP_TAG( PT_STRING8, 0x3A51) + +#define PR_CONTACT_VERSION PROP_TAG( PT_CLSID, 0x3A52) +#define PR_CONTACT_ENTRYIDS PROP_TAG( PT_MV_BINARY, 0x3A53) + +#define PR_CONTACT_ADDRTYPES PROP_TAG( PT_MV_TSTRING, 0x3A54) +#define PR_CONTACT_ADDRTYPES_W PROP_TAG( PT_MV_UNICODE, 0x3A54) +#define PR_CONTACT_ADDRTYPES_A PROP_TAG( PT_MV_STRING8, 0x3A54) + +#define PR_CONTACT_DEFAULT_ADDRESS_INDEX PROP_TAG( PT_LONG, 0x3A55) + +#define PR_CONTACT_EMAIL_ADDRESSES PROP_TAG( PT_MV_TSTRING, 0x3A56) +#define PR_CONTACT_EMAIL_ADDRESSES_W PROP_TAG( PT_MV_UNICODE, 0x3A56) +#define PR_CONTACT_EMAIL_ADDRESSES_A PROP_TAG( PT_MV_STRING8, 0x3A56) + + +#define PR_COMPANY_MAIN_PHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A57) +#define PR_COMPANY_MAIN_PHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A57) +#define PR_COMPANY_MAIN_PHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A57) + +#define PR_CHILDRENS_NAMES PROP_TAG( PT_MV_TSTRING, 0x3A58) +#define PR_CHILDRENS_NAMES_W PROP_TAG( PT_MV_UNICODE, 0x3A58) +#define PR_CHILDRENS_NAMES_A PROP_TAG( PT_MV_STRING8, 0x3A58) + + + +#define PR_HOME_ADDRESS_CITY PROP_TAG( PT_TSTRING, 0x3A59) +#define PR_HOME_ADDRESS_CITY_W PROP_TAG( PT_UNICODE, 0x3A59) +#define PR_HOME_ADDRESS_CITY_A PROP_TAG( PT_STRING8, 0x3A59) + +#define PR_HOME_ADDRESS_COUNTRY PROP_TAG( PT_TSTRING, 0x3A5A) +#define PR_HOME_ADDRESS_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A5A) +#define PR_HOME_ADDRESS_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A5A) + +#define PR_HOME_ADDRESS_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A5B) +#define PR_HOME_ADDRESS_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A5B) +#define PR_HOME_ADDRESS_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A5B) + +#define PR_HOME_ADDRESS_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A5C) +#define PR_HOME_ADDRESS_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A5C) +#define PR_HOME_ADDRESS_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A5C) + +#define PR_HOME_ADDRESS_STREET PROP_TAG( PT_TSTRING, 0x3A5D) +#define PR_HOME_ADDRESS_STREET_W PROP_TAG( PT_UNICODE, 0x3A5D) +#define PR_HOME_ADDRESS_STREET_A PROP_TAG( PT_STRING8, 0x3A5D) + +#define PR_HOME_ADDRESS_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A5E) +#define PR_HOME_ADDRESS_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A5E) +#define PR_HOME_ADDRESS_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A5E) + +#define PR_OTHER_ADDRESS_CITY PROP_TAG( PT_TSTRING, 0x3A5F) +#define PR_OTHER_ADDRESS_CITY_W PROP_TAG( PT_UNICODE, 0x3A5F) +#define PR_OTHER_ADDRESS_CITY_A PROP_TAG( PT_STRING8, 0x3A5F) + +#define PR_OTHER_ADDRESS_COUNTRY PROP_TAG( PT_TSTRING, 0x3A60) +#define PR_OTHER_ADDRESS_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A60) +#define PR_OTHER_ADDRESS_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A60) + +#define PR_OTHER_ADDRESS_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A61) +#define PR_OTHER_ADDRESS_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A61) +#define PR_OTHER_ADDRESS_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A61) + +#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A62) +#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A62) +#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A62) + +#define PR_OTHER_ADDRESS_STREET PROP_TAG( PT_TSTRING, 0x3A63) +#define PR_OTHER_ADDRESS_STREET_W PROP_TAG( PT_UNICODE, 0x3A63) +#define PR_OTHER_ADDRESS_STREET_A PROP_TAG( PT_STRING8, 0x3A63) + +#define PR_OTHER_ADDRESS_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A64) +#define PR_OTHER_ADDRESS_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A64) +#define PR_OTHER_ADDRESS_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A64) + + +/* + * Profile section properties + */ + +#define PR_STORE_PROVIDERS PROP_TAG( PT_BINARY, 0x3D00) +#define PR_AB_PROVIDERS PROP_TAG( PT_BINARY, 0x3D01) +#define PR_TRANSPORT_PROVIDERS PROP_TAG( PT_BINARY, 0x3D02) + +#define PR_DEFAULT_PROFILE PROP_TAG( PT_BOOLEAN, 0x3D04) +#define PR_AB_SEARCH_PATH PROP_TAG( PT_MV_BINARY, 0x3D05) +#define PR_AB_DEFAULT_DIR PROP_TAG( PT_BINARY, 0x3D06) +#define PR_AB_DEFAULT_PAB PROP_TAG( PT_BINARY, 0x3D07) + +#define PR_FILTERING_HOOKS PROP_TAG( PT_BINARY, 0x3D08) +#define PR_SERVICE_NAME PROP_TAG( PT_TSTRING, 0x3D09) +#define PR_SERVICE_NAME_W PROP_TAG( PT_UNICODE, 0x3D09) +#define PR_SERVICE_NAME_A PROP_TAG( PT_STRING8, 0x3D09) +#define PR_SERVICE_DLL_NAME PROP_TAG( PT_TSTRING, 0x3D0A) +#define PR_SERVICE_DLL_NAME_W PROP_TAG( PT_UNICODE, 0x3D0A) +#define PR_SERVICE_DLL_NAME_A PROP_TAG( PT_STRING8, 0x3D0A) +#define PR_SERVICE_ENTRY_NAME PROP_TAG( PT_STRING8, 0x3D0B) +#define PR_SERVICE_UID PROP_TAG( PT_BINARY, 0x3D0C) +#define PR_SERVICE_EXTRA_UIDS PROP_TAG( PT_BINARY, 0x3D0D) +#define PR_SERVICES PROP_TAG( PT_BINARY, 0x3D0E) +#define PR_SERVICE_SUPPORT_FILES PROP_TAG( PT_MV_TSTRING, 0x3D0F) +#define PR_SERVICE_SUPPORT_FILES_W PROP_TAG( PT_MV_UNICODE, 0x3D0F) +#define PR_SERVICE_SUPPORT_FILES_A PROP_TAG( PT_MV_STRING8, 0x3D0F) +#define PR_SERVICE_DELETE_FILES PROP_TAG( PT_MV_TSTRING, 0x3D10) +#define PR_SERVICE_DELETE_FILES_W PROP_TAG( PT_MV_UNICODE, 0x3D10) +#define PR_SERVICE_DELETE_FILES_A PROP_TAG( PT_MV_STRING8, 0x3D10) +#define PR_AB_SEARCH_PATH_UPDATE PROP_TAG( PT_BINARY, 0x3D11) +#define PR_PROFILE_NAME PROP_TAG( PT_TSTRING, 0x3D12) +#define PR_PROFILE_NAME_A PROP_TAG( PT_STRING8, 0x3D12) +#define PR_PROFILE_NAME_W PROP_TAG( PT_UNICODE, 0x3D12) + +/* + * Status object properties + */ + +#define PR_IDENTITY_DISPLAY PROP_TAG( PT_TSTRING, 0x3E00) +#define PR_IDENTITY_DISPLAY_W PROP_TAG( PT_UNICODE, 0x3E00) +#define PR_IDENTITY_DISPLAY_A PROP_TAG( PT_STRING8, 0x3E00) +#define PR_IDENTITY_ENTRYID PROP_TAG( PT_BINARY, 0x3E01) +#define PR_RESOURCE_METHODS PROP_TAG( PT_LONG, 0x3E02) +#define PR_RESOURCE_TYPE PROP_TAG( PT_LONG, 0x3E03) +#define PR_STATUS_CODE PROP_TAG( PT_LONG, 0x3E04) +#define PR_IDENTITY_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3E05) +#define PR_OWN_STORE_ENTRYID PROP_TAG( PT_BINARY, 0x3E06) +#define PR_RESOURCE_PATH PROP_TAG( PT_TSTRING, 0x3E07) +#define PR_RESOURCE_PATH_W PROP_TAG( PT_UNICODE, 0x3E07) +#define PR_RESOURCE_PATH_A PROP_TAG( PT_STRING8, 0x3E07) +#define PR_STATUS_STRING PROP_TAG( PT_TSTRING, 0x3E08) +#define PR_STATUS_STRING_W PROP_TAG( PT_UNICODE, 0x3E08) +#define PR_STATUS_STRING_A PROP_TAG( PT_STRING8, 0x3E08) +#define PR_X400_DEFERRED_DELIVERY_CANCEL PROP_TAG( PT_BOOLEAN, 0x3E09) +#define PR_HEADER_FOLDER_ENTRYID PROP_TAG( PT_BINARY, 0x3E0A) +#define PR_REMOTE_PROGRESS PROP_TAG( PT_LONG, 0x3E0B) +#define PR_REMOTE_PROGRESS_TEXT PROP_TAG( PT_TSTRING, 0x3E0C) +#define PR_REMOTE_PROGRESS_TEXT_W PROP_TAG( PT_UNICODE, 0x3E0C) +#define PR_REMOTE_PROGRESS_TEXT_A PROP_TAG( PT_STRING8, 0x3E0C) +#define PR_REMOTE_VALIDATE_OK PROP_TAG( PT_BOOLEAN, 0x3E0D) + +/* + * Display table properties + */ + +#define PR_CONTROL_FLAGS PROP_TAG( PT_LONG, 0x3F00) +#define PR_CONTROL_STRUCTURE PROP_TAG( PT_BINARY, 0x3F01) +#define PR_CONTROL_TYPE PROP_TAG( PT_LONG, 0x3F02) +#define PR_DELTAX PROP_TAG( PT_LONG, 0x3F03) +#define PR_DELTAY PROP_TAG( PT_LONG, 0x3F04) +#define PR_XPOS PROP_TAG( PT_LONG, 0x3F05) +#define PR_YPOS PROP_TAG( PT_LONG, 0x3F06) +#define PR_CONTROL_ID PROP_TAG( PT_BINARY, 0x3F07) +#define PR_INITIAL_DETAILS_PANE PROP_TAG( PT_LONG, 0x3F08) + +/* + * Secure property id range + */ + +#define PROP_ID_SECURE_MIN 0x67F0 +#define PROP_ID_SECURE_MAX 0x67FF + + +#endif /* MAPITAGS_H */ diff -urN exim-4.41-orig/src/version.c exim-4.41/src/version.c --- exim-4.41-orig/src/version.c Thu Jul 22 10:46:53 2004 +++ exim-4.41/src/version.c Wed Aug 18 17:12:12 2004 @@ -11,6 +11,7 @@ #define THIS_VERSION "4.41" +#define EXISCAN_VERSION "25" /* The header file cnumber.h contains a single line containing the @@ -40,6 +41,7 @@ version_cnumber_format = US"%d\0<>"; sprintf(CS version_cnumber, CS version_cnumber_format, cnumber); version_string = US THIS_VERSION "\0<>"; +exiscan_version_string = US EXISCAN_VERSION; Ustrcpy(today, __DATE__); if (today[4] == ' ') today[4] = '0';