Menu

#9 local policy API

open
5
2006-09-27
2005-06-20
No

We want to detect zombie PCs. When PCs connect using
an internal private IP, and a bogus domain, they are
most likely a zombie. Currently, the milter
application uses the "internal_domains" config item to
restrict internal connections to the listed domains.

It would be nice to have the full flexibility of SPF
for this job, but the public SPF records can't be used
because the connections are on private IPs. I would
like a way to extend the public SPF records with local
additions for internal connections. The list of
extended domains would be relatively small (a single
enterprise), so could be supplied via a python dict.
The config might look like this in the milter
application (SPF module would just get the dict):

[spf]
mycompany.com = ip4:192.168.0.0/24
myalias.net = ip4:192.168.0.0/24 ip4:192.168.1.0/24

Discussion

  • Scott Kitterman

    Scott Kitterman - 2005-06-22

    Logged In: YES
    user_id=1300068

    I think we also want to add a layer of separation between
    SPF results and SMTP results. I.e. SPF = Pass, Fail, None,
    Unknown, Softfail, PermError, TempError - SMTP = 250, 450, 550.

     
  • Stuart D. Gathman

    Logged In: YES
    user_id=142072

    It just occured to me that the SPF module should not even
    have to deal with a dict for local policy. There should be
    a local parameter when creating a query. The caller can get
    it from a dict or database as needed.

    For instance,
    q =
    spf.query(self.connectip,self.canonfrom,self.hello_name,receiver=receiver,
    local="ip4:1.2.3.4")

     
  • Stuart D. Gathman

    Logged In: YES
    user_id=142072

    The SMTP result returned along with the SPF result in a
    tuple is a suggestion only. You can remove it if you wish,
    since we are already breaking the API by changing the result
    set. It is mostly ignored.

    It might be cleaner, since you are changing the API, to
    return a simple string for the result, and have an attribute
    for the explanation. For example,

    q =
    spf.query(self.connectip,'@'.join(t),self.hello_name,receiver=receiver)
    res = q.check() # was res,code,txt = q.check()
    txt = q.explanation

    But this is good too:
    res,txt = q.check()

     
  • Scott Kitterman

    Scott Kitterman - 2005-07-25

    Logged In: YES
    user_id=1300068

    # Recommended SMTP codes for certain SPF results. For
    results not in
    # this table the recommendation is to accept the message.
    # The softfail result requires special processing.

    SMTP_CODES = {
    'fail': (550,'5.7.1'),
    'temperror': (451,'4.4.3'),
    'permerror': (550,'5.5.2'),
    'softfail':(451,'4.3.0')

     
  • Scott Kitterman

    Scott Kitterman - 2005-07-25

    Logged In: YES
    user_id=1300068

    Should this also return the SPF-received header field for
    the Milter to write into the message header?

     
  • Scott Kitterman

    Scott Kitterman - 2005-07-28

    Logged In: YES
    user_id=1300068

    I've changed my mind on this. pySPF 1.6 (the Terrence
    version) is the preferred SPF library for one Postfix policy
    daemon and has also recently been added to Debian.

    So, if we want to get other users to move up to the more
    modern/complete version here, we have to maintain a
    compatible result set. I think that's res, code, txt, but
    I'm going to check to make sure.

    Rather than pass only the SPF result out with and
    explanation, I'm going to pass the local policy in so that
    the returned unified result can be returned with an
    appropriate SMTP code.

     
  • Scott Kitterman

    Scott Kitterman - 2005-08-01

    Logged In: YES
    user_id=1300068

    It turns out that the current local implementation is done
    the same way the Mail::SPF::Query and Libspf2 do it. Had a
    conversation with Wayne about it on #SPF today (below).
    Bottom line is that I think that pySPF as it ought to
    function the way it does now (local after SPF) as a default,
    but that the local policy module ought to give this as a
    policy option.

    Full transcript is attached. Here are the highlights:

    Message from grumpy at 02:41:52 pm
    You might want to make the "local policy" stuff work
    like M:S:Q and libspf2. (I copied M;S:Q semantics.) They are
    well thought out, not obvious, and somewhat standardized.
    Message from grumpy at 02:42:25 pm
    In particular, local policies need to be ignore for
    records like "v=spf1 -all"
    Message to #spf at 02:42:33 pm
    OK. Is there documentation I can look at...
    Message to #spf at 02:44:21 pm
    I certainly have no desire to be gratuitously incompatible.
    * grumpy returns with lunch
    Message from grumpy at 02:49:57 pm
    I'm afraid the best documentation that I know if is
    somewhat burried in the libspf2 source...
    Message from grumpy at 02:50:05 pm
    let me get you a URL or something.
    Message to #spf at 02:50:45 pm
    Thanks. I'd rather avoid having to learn Perl to write
    the Python. Of course if the option is to learn C, maybe
    Perl isn't so bad...
    Message from grumpy at 02:52:30 pm
    google for "spf local policy" returns as the first hit:
    http://www.libspf2.org/docs/api.html
    Message from grumpy at 02:52:41 pm
    amazingly, that has the description.
    Message from grumpy at 02:52:46 pm
    thanks Shevek!
    Message from grumpy at 02:54:06 pm
    the description is about half way down the web page
    under the SPF_compile_local_policy() function.

     
  • Scott Kitterman

    Scott Kitterman - 2005-08-02

    Logged In: YES
    user_id=1300068

    Upon further investigation it appears to me that the current
    local policy implementation doesn't actually work.

     
  • Stuart D. Gathman

    Logged In: YES
    user_id=142072

    I said as much. It is rarely useful to apply after public
    policy. Libspf2 has some neat ideas (especially compling
    the SPF records), but their local_policy idea is too
    complex. Maybe it is something different from what libspf2
    calls "local policy", but what I need in pymilter is to put
    some mechanisms in *front* of the record for selected domains.

    For instance, blockbuster.com sends event email with a MAIL
    FROM: <blockbuster@custhelp.com>. But custhelp.com has an
    SPF records that flags this mail as a forgery:

    2005Aug02 15:24:03 [153181] connect from
    smtp1.online.blockbuster.com at ('129.33.92.74', 40308) EXTERNAL
    2005Aug02 15:24:03 [153181] hello from
    smtp1.online.blockbuster.com
    2005Aug02 15:24:04 [153181] mail from
    <blockbuster@custhelp.com> ('SIZE=1773',)
    2005Aug02 15:24:04 [153181] REJECT: SPF fail 550 SPF fail:
    see
    http://spf.pobox.com/why.html?sender=blockbuster@custhelp.com&ip=129.33.92.74

    Both companies have ignored months worth of emails to
    postmaster and complaints on their website. Their solution
    is for me to "whitelist" blockbuster@custhelp.com. Of
    course, I also get *truly* forged email from
    blockbuster@custhelp.com, so I can't simply skip SPF for
    that MAIL FROM. Instead, I want to insert the following in
    front of custhelp.com SPF record:

    [local_policy]
    custhelp.com = ptr:blockbuster.com

    Whenever I fetch the public custhelp.com SPF record, currently:

    v=spf1 ip4:63.240.89.0/24 ip4:63.240.103.0/26
    ip4:216.136.162.64/26 ip4:216.136.229.0/24
    ip4:216.136.168.64/27 ip4:206.17.168.0/25 ip4:64.79.35.0/24 -all

    I stick the above in front to get:

    v=spf1 ptr:blockbuster.com ip4:63.240.89.0/24
    ip4:63.240.103.0/26 ip4:216.136.162.64/26
    ip4:216.136.229.0/24 ip4:216.136.168.64/27
    ip4:206.17.168.0/25 ip4:64.79.35.0/24 -all

    This lets me whitelist just the blockbuster braindamage
    without letting in all the real forgeries.

    Similarly, I can use the above for local domains. If my
    local domain is example.com:

    [local_policy]
    example.com = ip4:192.168.45.0/24 ip4:127.0.0.0/8

    This says to accept the example.com MAIL FROM domain from
    those internal networks, even though they are not mentioned
    in the public record.

     
  • Scott Kitterman

    Scott Kitterman - 2005-08-03

    Logged In: YES
    user_id=1300068

    The problem is that the current code puts the local policy
    AFTER the all and so unless the local policy is a modifier,
    it never gets acted on.

    My intent is to make pySPF local work exactly like
    Mail::SPF::Query and libspf2 since they are the reference
    implementations. I'll also have a switch to make it work
    the way you want.

    You are right. Their way is complex (you have to parse out
    the record, find the all or redirect= if any and then add
    the local policy right before it.... I do want to make sure
    we are as compatible as possible, but then give options.
    The complexity that their way requires will be there, but
    I'll write it so that if the switch is thrown, the complex
    code will never get excercised.

     
  • Scott Kitterman

    Scott Kitterman - 2005-08-18

    Logged In: YES
    user_id=1300068

    Local will now be applied after the last non-fail mechanism
    in the record, so it at least functions now. Still to do is
    to add something to the explanation to indicate it's a
    result of local policy and to update the received SPF
    header. Once that's done, it'll work like M:S:Q and libspf2.

    Then I'll add a switch to make it work like you want.

     
  • Scott Kitterman

    Scott Kitterman - 2006-09-27
    • labels: 748796 --> milter application
    • assigned_to: kitterma --> customdesigned
     
  • Scott Kitterman

    Scott Kitterman - 2006-09-27

    Logged In: YES
    user_id=1300068

    SPF library is not the place to do this.

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.