diff -urN exim-4.22-orig/OS/Makefile-Base exim-4.22/OS/Makefile-Base --- exim-4.22-orig/OS/Makefile-Base Mon Aug 18 14:52:50 2003 +++ exim-4.22/OS/Makefile-Base Mon Aug 18 16:17:22 2003 @@ -247,14 +247,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 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 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 spool_in.o spool_mbox.o spool_out.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 \ @@ -466,6 +466,7 @@ 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 @@ -479,6 +480,7 @@ 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 moan.o: $(HDRS) moan.c os.o: $(HDRS) os.c @@ -487,6 +489,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 @@ -495,11 +498,14 @@ sieve.o: $(HDRS) sieve.c smtp_in.o: $(HDRS) smtp_in.c smtp_out.o: $(HDRS) smtp_out.c +spam.o: $(HDRS) spam.c spool_in.o: $(HDRS) spool_in.c +spool_mbox.o: $(HDRS) spool_mbox.c spool_out.o: $(HDRS) spool_out.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.22-orig/README.EXISCAN exim-4.22/README.EXISCAN --- exim-4.22-orig/README.EXISCAN Thu Jan 1 01:00:00 1970 +++ exim-4.22/README.EXISCAN Mon Aug 18 16:17:22 2003 @@ -0,0 +1 @@ +Please refer to doc/exiscan-acl-spec.txt diff -urN exim-4.22-orig/doc/exiscan-acl-examples.txt exim-4.22/doc/exiscan-acl-examples.txt --- exim-4.22-orig/doc/exiscan-acl-examples.txt Thu Jan 1 01:00:00 1970 +++ exim-4.22/doc/exiscan-acl-examples.txt Mon Aug 18 16:17:22 2003 @@ -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.22-orig/doc/exiscan-acl-spec.txt exim-4.22/doc/exiscan-acl-spec.txt --- exim-4.22-orig/doc/exiscan-acl-spec.txt Thu Jan 1 01:00:00 1970 +++ exim-4.22/doc/exiscan-acl-spec.txt Mon Aug 18 16:17:22 2003 @@ -0,0 +1,483 @@ +-------------------------------------------------------------- +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 facilities: + + - MIME unpacking, sanity checking, file extension blocking + - Antivirus using 3rd party scanners + - Antispam using SpamAssassin + - Regular expression match against headers and body + +These facilities are hooked into exim by adding new conditions +to exim's ACL system. 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. + +The exiscan-acl patch also defines several expansion variables +that can be used to customise the error responses sent to the +remote server. + +The default exim configure file contains commented +configuration examples for all facilites. + + +0. Overall concept +-------------------------------------------------------------- + +The exiscan-acl patch adds the following conditions +(facilities), which can ONLY be used in the ACL after DATA +(acl_smtp_data): + +- demime (MIME unpacking and file extension checks) +- regex (match regular expressions against message headers + and body) +- malware (attach 3rd party virus/malware scanner) +- spam (attach SpamAssassin) + +Each of these facilities has its own chapter further below in +this document. There is also a commented sample configuration +in the "configure" file of the exim distribution. + +The exiscan facility also adds the 'fakereject' ACL control +statement that can be used to reject and accept a message at +the same time. It is described in an extra chapter below. + +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 +condition 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 +"demime" condition has been used, this directory will also +contain files produced by the MIME decoder. + + +1. The "demime" facility + MIME unpacking, sanity checking and file extension blocking +-------------------------------------------------------------- + +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 +--------------------- */ + + + +2. The "spam" facility + Antispam measures with SpamAssassin +-------------------------------------------------------------- + +The "spam" facility 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 it's 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 + +If you use the above mentioned default, you do NOT need to set +this option. + +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 it's +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. + +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}} +----------------- */ + + + +3. The "regex" facility + Match headers and body lines of the message against regular + expressions +-------------------------------------------------------------- + +The "regex" condition takes one or more regular expressions as +arguments and matches them against the full message, that is +all headers and the complete body. It is particularly useful +to filter trash that cannot be recognized by the spam or +malware conditions. With large messages, this condition can be +fairly CPU-intensive. + +The regular expressions are matched linewise, with a maximum +line length of 32k characters. + +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 condition returns true if one of the regular expressions +has matched a line of the message. The $regex_match_string +variable is then set up and contains the matching regular +expression. + + + +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 + + + kavdaemon Kapersky's kavdaemon is a daemon-type scanner. + You can get a trial version at + http://www.kapersky.com. 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 = * +---------------------- */ + + + +5. 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. + + + +-------------------------------------------------------------- +End of file +-------------------------------------------------------------- diff -urN exim-4.22-orig/exim_monitor/em_globals.c exim-4.22/exim_monitor/em_globals.c --- exim-4.22-orig/exim_monitor/em_globals.c Mon Aug 18 14:52:59 2003 +++ exim-4.22/exim_monitor/em_globals.c Mon Aug 18 16:17:22 2003 @@ -124,6 +124,8 @@ BOOL deliver_manual_thaw = FALSE; BOOL dont_deliver = FALSE; +BOOL fake_reject = FALSE; + header_line *header_last = NULL; header_line *header_list = NULL; @@ -133,6 +135,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.22-orig/scripts/MakeLinks exim-4.22/scripts/MakeLinks --- exim-4.22-orig/scripts/MakeLinks Mon Aug 18 14:52:52 2003 +++ exim-4.22/scripts/MakeLinks Mon Aug 18 16:17:22 2003 @@ -166,6 +166,7 @@ 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 @@ -173,8 +174,10 @@ ln -s ../src/macros.h macros.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/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/buildconfig.c buildconfig.c @@ -184,6 +187,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 @@ -202,6 +206,7 @@ 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/moan.c moan.c ln -s ../src/parse.c parse.c @@ -210,6 +215,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 @@ -218,13 +224,16 @@ 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/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/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.22-orig/src/acl.c exim-4.22/src/acl.c --- exim-4.22-orig/src/acl.c Mon Aug 18 14:52:52 2003 +++ exim-4.22/src/acl.c Wed Sep 3 21:51:55 2003 @@ -7,6 +7,8 @@ /* Code for handling Access Control Lists (ACLs) */ +/* This file has been modified by the exiscan-acl patch. */ + #include "exim.h" @@ -32,19 +34,19 @@ /* 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, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY, ACLC_DEMIME, ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS, - ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_MESSAGE, ACLC_RECIPIENTS, - ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY }; + ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_MALWARE, ACLC_MESSAGE, ACLC_RECIPIENTS, + ACLC_REGEX, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_SPAM, ACLC_VERIFY }; /* ACL conditions/modifiers: "delay", "control", "endpass", "message", -"log_message", and "set" are modifiers that look like conditions but always +"log_message", "set" and "spam" 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"message", - US"recipients", US"sender_domains", US"senders", US"set", US"verify" }; + US"control", US"delay", US"demime", US"dnslists", US"domains", US"encrypted", + US"endpass", US"hosts", US"local_parts", US"log_message", US"malware", US"message", + US"recipients", US"regex", US"sender_domains", US"senders", US"set", US"spam", US"verify" }; /* Flags to indicate for which conditions /modifiers a string expansion is done at the outer level. In the other cases, expansion already occurs in the @@ -56,6 +58,7 @@ TRUE, /* condition */ TRUE, /* control */ TRUE, /* delay */ + TRUE, /* demime */ TRUE, /* dnslists */ FALSE, /* domains */ FALSE, /* encrypted */ @@ -63,11 +66,14 @@ FALSE, /* hosts */ FALSE, /* local_parts */ TRUE, /* log_message */ + TRUE, /* malware */ TRUE, /* message */ FALSE, /* recipients */ + TRUE, /* regex */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ + TRUE, /* spam */ TRUE /* verify */ }; @@ -79,6 +85,7 @@ FALSE, /* condition */ TRUE, /* control */ TRUE, /* delay */ + FALSE, /* demime */ FALSE, /* dnslists */ FALSE, /* domains */ FALSE, /* encrypted */ @@ -86,11 +93,14 @@ FALSE, /* hosts */ FALSE, /* local_parts */ TRUE, /* log_message */ + FALSE, /* malware */ TRUE, /* message */ FALSE, /* recipients */ + FALSE, /* regex */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ + FALSE, /* spam */ FALSE /* verify */ }; @@ -110,6 +120,13 @@ (1<domain, &arg, 0, &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, &deliver_domain_data); @@ -1748,6 +1805,10 @@ smtp_command_argument = deliver_domain = deliver_localpart = deliver_address_data = NULL; +/* Remove spooled mbox and demimed files. +Will immediately return if no files had been created */ +unspool_mbox(); + /* A DISCARD response is permitted only for message ACLs */ if (rc == DISCARD) diff -urN exim-4.22-orig/src/configure.default exim-4.22/src/configure.default --- exim-4.22-orig/src/configure.default Mon Aug 18 14:52:53 2003 +++ exim-4.22/src/configure.default Mon Aug 18 16:17:22 2003 @@ -108,6 +108,25 @@ # You should not change that setting until you understand how ACLs work. +# The following ACL entry is used if you want to do content scanning with the +# exiscan-acl patch. When you uncomment this line, you must also review the +# acl_check_content entry in the ACL section further below. + +# 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 @@ -306,6 +325,52 @@ deny message = relay not permitted +# This access control list is used for content scanning with the exiscan-acl +# patch. You must also uncomment the entry for acl_smtp_data (scroll up), +# otherwise the ACL 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_content: + + # First unpack MIME containers and reject serious errors. + deny message = This message contains a MIME error ($demime_reason) + demime = * + condition = ${if >{$demime_errorlevel}{2}{1}{0}} + + # Reject typically wormish file extensions. There is almost no + # sense in sending such files by email. + deny message = This message contains an unwanted file extension ($found_extension) + demime = scr:vbs:bat:lnk:pif + + # Reject virus infested messages. + deny message = This message contains malware ($malware_name) + malware = * + + # Reject messages containing "viagra" in all kinds of whitespace/case combinations + # WARNING: this is an example ! + deny message = This message matches a blacklisted regular expression ($regex_match_string) + regex = [Vv] *[Ii] *[Aa] *[Gg] *[Rr] *[Aa] + + # 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.22-orig/src/demime.c exim-4.22/src/demime.c --- exim-4.22-orig/src/demime.c Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/demime.c Fri Aug 29 11:28:08 2003 @@ -0,0 +1,1273 @@ +/************************************************* +* 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) ) { + 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') { + /* look if next char is '\t' or ' ' */ + c = fgetc(f); + if (c == EOF) break; + if ( (c == '\t') || (c == ' ') ) continue; + /* end of the header, terminate with ';' */ + ungetc(c,f); + 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[strlen(line)+1]; + unsigned int i,j=0; + + /* check for '--' first */ + if (Ustrncmp(line,"--",2) == 0) { + + /* strip tab and space */ + for (i = 2; i < strlen(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.22-orig/src/demime.h exim-4.22/src/demime.h --- exim-4.22-orig/src/demime.h Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/demime.h Mon Aug 18 16:17:22 2003 @@ -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.22-orig/src/exim.c exim-4.22/src/exim.c --- exim-4.22-orig/src/exim.c Mon Aug 18 14:52:54 2003 +++ exim-4.22/src/exim.c Fri Aug 29 13:47:37 2003 @@ -1417,6 +1417,7 @@ 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); } else badarg = TRUE; diff -urN exim-4.22-orig/src/expand.c exim-4.22/src/expand.c --- exim-4.22-orig/src/expand.c Mon Aug 18 14:52:54 2003 +++ exim-4.22/src/expand.c Mon Aug 18 16:17:22 2003 @@ -219,11 +219,14 @@ { "caller_uid", vtype_int, &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 }, { "domain", vtype_stringptr, &deliver_domain }, { "domain_data", vtype_stringptr, &deliver_domain_data }, + { "found_extension", vtype_stringptr, &found_extension }, { "home", vtype_stringptr, &deliver_home }, { "host", vtype_stringptr, &deliver_host }, { "host_address", vtype_stringptr, &deliver_host_address }, @@ -242,6 +245,7 @@ { "local_part_suffix", vtype_stringptr, &deliver_localpart_suffix }, { "local_scan_data", vtype_stringptr, &local_scan_data }, { "localhost_number", vtype_int, &host_number }, + { "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 }, @@ -276,6 +280,7 @@ { "received_protocol", vtype_stringptr, &received_protocol }, { "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, &return_size_limit }, @@ -303,6 +308,10 @@ { "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 }, { "spool_directory", vtype_stringptr, &spool_directory }, { "thisaddress", vtype_stringptr, &filter_thisaddress }, { "tls_certificate_verified", vtype_int, &tls_certificate_verified }, diff -urN exim-4.22-orig/src/functions.h exim-4.22/src/functions.h --- exim-4.22-orig/src/functions.h Mon Aug 18 14:52:54 2003 +++ exim-4.22/src/functions.h Thu Aug 28 10:11:59 2003 @@ -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_init(BOOL, BOOL); @@ -117,6 +118,7 @@ extern void log_close_all(void); +extern int malware(uschar **); extern int match_address_list(uschar *, BOOL, uschar **, unsigned int *, int, int); extern int match_check_list(uschar **, int, tree_node **, unsigned int **, @@ -173,6 +175,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); @@ -230,6 +233,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 *); @@ -280,6 +285,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.22-orig/src/globals.c exim-4.22/src/globals.c --- exim-4.22-orig/src/globals.c Mon Aug 18 14:52:54 2003 +++ exim-4.22/src/globals.c Fri Aug 29 13:51:45 2003 @@ -290,6 +290,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= @@ -404,6 +405,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; @@ -433,6 +437,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; @@ -442,12 +447,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; uschar *freeze_tell = NULL; uschar *gecos_name = NULL; @@ -578,6 +585,7 @@ uschar *lookup_value = NULL; macro_item *macros = NULL; +uschar *malware_name = NULL; int max_username_length = 0; int message_age = 0; uschar *message_body = NULL; @@ -697,6 +705,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; @@ -863,6 +872,11 @@ 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; BOOL split_spool_directory = FALSE; uschar *spool_directory = US SPOOL_DIRECTORY "\0<--------------Space to patch spool_directory->"; diff -urN exim-4.22-orig/src/globals.h exim-4.22/src/globals.h --- exim-4.22-orig/src/globals.h Mon Aug 18 14:52:54 2003 +++ exim-4.22/src/globals.h Fri Aug 29 13:51:05 2003 @@ -131,6 +131,7 @@ 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 */ @@ -212,6 +213,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 */ @@ -241,6 +245,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 */ @@ -249,12 +254,14 @@ 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 */ extern uschar *filter_test; /* Run as a filter tester on this file */ extern uschar *filter_thisaddress; /* For address looping */ extern int finduser_retries; /* Retry count for getpwnam() */ +extern uschar *found_extension; /* demime acl condition: file extension found */ extern uschar *freeze_tell; /* Message on (some) freezings */ extern uschar *gecos_name; /* To be expanded when pattern matches */ @@ -327,6 +334,7 @@ extern uschar *lookup_value; /* Value looked up from file */ extern macro_item *macros; /* Configuration macros */ +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 */ @@ -430,6 +438,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 */ @@ -518,6 +527,11 @@ 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) */ extern uschar *spool_directory; /* Name of spool directory */ extern int string_datestamp_offset;/* After insertion by string_format */ extern BOOL strip_excess_angle_brackets; /* Surrounding route-addrs */ diff -urN exim-4.22-orig/src/malware.c exim-4.22/src/malware.c --- exim-4.22-orig/src/malware.c Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/malware.c Fri Aug 29 09:42:39 2003 @@ -0,0 +1,1157 @@ +/************************************************* +* 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 + +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; + }; + + /* 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(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); + } + /* ----------------------------------------------------------------------- */ + + /* "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 lower nibble */ + kav_rc = tmpbuf[0] & 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)) { + + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + malware_name = malware_name_buffer; + + /* read the report, if available */ + if( tmpbuf[1] == 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]; + 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"); + + 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)); + 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); + 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); + 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); + + 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 */ + 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; + 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; + }; +} + + + +/* ============= 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; + } + } + return 0; +} + +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.22-orig/src/readconf.c exim-4.22/src/readconf.c --- exim-4.22-orig/src/readconf.c Mon Aug 18 14:52:56 2003 +++ exim-4.22/src/readconf.c Mon Aug 18 16:17:22 2003 @@ -152,6 +152,7 @@ { "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 }, { "bounce_message_file", opt_stringptr, &bounce_message_file }, { "bounce_message_text", opt_stringptr, &bounce_message_text }, @@ -304,6 +305,7 @@ { "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 }, { "strip_excess_angle_brackets", opt_bool, &strip_excess_angle_brackets }, diff -urN exim-4.22-orig/src/receive.c exim-4.22/src/receive.c --- exim-4.22-orig/src/receive.c Mon Aug 18 14:52:57 2003 +++ exim-4.22/src/receive.c Mon Aug 18 16:17:22 2003 @@ -2914,12 +2914,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.22-orig/src/regex.c exim-4.22/src/regex.c --- exim-4.22-orig/src/regex.c Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/regex.c Mon Aug 18 16:17:22 2003 @@ -0,0 +1,110 @@ +/************************************************* +* 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" + +/* 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]; + +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; + + /* reset expansion variable */ + regex_match_string = NULL; + + /* 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, + "regex acl condition: error while creating mbox spool file"); + return DEFER; + }; + + /* 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) { + 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; + fclose(mbox_file); + return OK; + }; + re_list_item = re_list_item->next; + } while (re_list_item != NULL); + }; + + fclose(mbox_file); + + /* no matches ... */ + return FAIL; +} diff -urN exim-4.22-orig/src/smtp_in.c exim-4.22/src/smtp_in.c --- exim-4.22-orig/src/smtp_in.c Mon Aug 18 14:52:57 2003 +++ exim-4.22/src/smtp_in.c Mon Aug 18 16:17:22 2003 @@ -772,6 +772,7 @@ acl_warn_headers = NULL; queue_only_policy = FALSE; deliver_freeze = FALSE; /* Can be set by ACL */ +fake_reject = FALSE; /* Can be set by ACL */ sender_address = NULL; raw_sender = NULL; /* After SMTP rewrite, before qualifying */ sender_address_unrewritten = NULL; /* Set only after verify rewrite */ diff -urN exim-4.22-orig/src/spam.c exim-4.22/src/spam.c --- exim-4.22-orig/src/spam.c Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/spam.c Thu Aug 28 10:20:01 2003 @@ -0,0 +1,265 @@ +/************************************************* +* 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 tcp_addr[24]; + unsigned int tcp_port; + 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; + + /* 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; + }; + + /* contact spamd */ + spamd_sock = ip_socket(SOCK_STREAM, AF_INET); + if (spamd_sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error creating IP socket for spamd"); + fclose(mbox_file); + return DEFER; + }; + + if (ip_bind(spamd_sock, AF_INET, US"0.0.0.0", 0) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: bind socket for spamd failed: %s",strerror(errno)); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + }; + + /* grok spamd address and port */ + if( sscanf(CS spamd_address, "%s %u", tcp_addr, &tcp_port) != 2 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: invalid spamd address: '%s'", spamd_address); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + }; + + if (ip_connect(spamd_sock, AF_INET, tcp_addr, tcp_port, 5) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: spamd connection to %s, port %u failed: %s", tcp_addr, tcp_port, 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*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.22-orig/src/spam.h exim-4.22/src/spam.h --- exim-4.22-orig/src/spam.h Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/spam.h Mon Aug 18 16:17:22 2003 @@ -0,0 +1,23 @@ +/************************************************* +* 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 + diff -urN exim-4.22-orig/src/spool_in.c exim-4.22/src/spool_in.c --- exim-4.22-orig/src/spool_in.c Mon Aug 18 14:52:58 2003 +++ exim-4.22/src/spool_in.c Mon Aug 18 16:17:22 2003 @@ -239,6 +239,7 @@ body_linecount = 0; deliver_firsttime = FALSE; deliver_freeze = FALSE; +fake_reject = FALSE; deliver_frozen_at = 0; deliver_manual_thaw = FALSE; /* dont_deliver must NOT be reset */ @@ -248,6 +249,7 @@ interface_port = 0; local_error_message = FALSE; local_scan_data = NULL; +spam_score_int = NULL; message_linecount = 0; received_protocol = NULL; recipients_list = NULL; @@ -347,6 +349,8 @@ 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); 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.22-orig/src/spool_mbox.c exim-4.22/src/spool_mbox.c --- exim-4.22-orig/src/spool_mbox.c Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/spool_mbox.c Thu Aug 28 10:21:09 2003 @@ -0,0 +1,157 @@ +/************************************************* +* 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; + +/* 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; + + 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; + }; + + /* 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); + 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; + file_extensions = NULL; + spam_ok = 0; + malware_ok = 0; + + if (spool_mbox_ok) { + uschar mbox_path[1024]; + uschar file_path[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + spool_mbox_ok = 0; + + snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, 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, message_id, entry->d_name); + if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) ) + n = unlink(CS file_path); + } while (n > -1); + + closedir(tempdir); + + /* remove directory */ + n = rmdir(CS mbox_path); + }; +} diff -urN exim-4.22-orig/src/spool_out.c exim-4.22/src/spool_out.c --- exim-4.22-orig/src/spool_out.c Mon Aug 18 14:52:58 2003 +++ exim-4.22/src/spool_out.c Mon Aug 18 16:17:22 2003 @@ -200,6 +200,7 @@ 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"); diff -urN exim-4.22-orig/src/tnef.c exim-4.22/src/tnef.c --- exim-4.22-orig/src/tnef.c Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/tnef.c Mon Sep 1 15:39:39 2003 @@ -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 > 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 > 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.22-orig/src/tnef.h exim-4.22/src/tnef.h --- exim-4.22-orig/src/tnef.h Thu Jan 1 01:00:00 1970 +++ exim-4.22/src/tnef.h Fri Aug 29 09:38:02 2003 @@ -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.22-orig/src/version.c exim-4.22/src/version.c --- exim-4.22-orig/src/version.c Mon Aug 18 14:52:59 2003 +++ exim-4.22/src/version.c Thu Sep 4 09:22:30 2003 @@ -11,7 +11,7 @@ #define THIS_VERSION "4.22" - +#define EXISCAN_VERSION "12" /* The header file cnumber.h contains a single line containing the compilation number, making it easy to have it updated automatically. @@ -40,6 +40,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';