Skip to main content

Ralsina.Me — Roberto Alsina's website

Posts about RaSPF (old posts, page 1)

My SPF library kinda works

RaSPF, my at­tempt­ed port of PySPF to C is now at a very spe­cial point in its life:

The pro­vid­ed CLI ap­pli­ca­tion can check SPF records and tell you what you should do with them!

Here's an ex­am­ple:

[ralsina@monty build]$ ./raspfquery --ip=192.0.2.1 --sender=03.spf1-test.mailzone.com --helo=03.spf1-test.mailzone.com
Checking SPF with:

sender: 03.spf1-test.mailzone.com
helo:   03.spf1-test.mailzone.com
ip:     192.0.2.1


response:       softfail
code:           250
explanation:    domain owner discourages use of this host

Is that cor­rec­t? Ap­par­ent­ly yes!

[ralsina@monty pyspf-2.0.2]$ python spf.py 192.0.2.1 03.spf1-test.mailzone.com 03.spf1-test.mailzone.com
('softfail', 250, 'domain owner discourages use of this host')

Is it use­ful? Sure­ly you jest!

There are still the fol­low­ing prob­lem­s:

  • The mem­o­ry man­age­­ment is un­ex­is­­tant

  • I need to hack a way to run the of­­fi­­cial SPF test suite so I can see how well it works and that it works ex­ac­t­­ly as PySPF

  • It prob­a­bly will seg­­fault on many places

  • I am chang­ing the er­ror han­dling to be ex­­cep­­tion-based, thanks to EX­CC

  • The IPv6 sup­­port is be­tween iffy and not there

  • There is no sup­­port for SPF (type 99) DNS record­s, on­­ly TXT records (need to hack the udns li­brary)

But re­al­ly, this should be about 60% of the work, and it does work for some cas­es, which is more than I re­al­ly ex­pect­ed at the be­gin­ning.

Here's the whole source code of the sam­ple ap­pli­ca­tion (ex­cept for CLI op­tion pro­cess­ing):

spf_init();
spf_response r=spf_check(ip,sender,helo,0,0);
printf ("\nresponse:\t%s\ncode:\t\t%d\nexplanation:\t\t%s\n",
        r.response,r.code,r.explanation);

My SPF lib improving

It now can do a bunch of things like ex­pand­ing macros and (in some cas­es) val­i­dat­ing mech­a­nism­s.

I am mak­ing very heavy use of unit test­ing, be­cause it's a pret­ty com­plex piece and each func­tion needs to do ex­act­ly the right thing or ev­ery­thing else fails (it's pret­ty hard to fig­ure out where it will fail ;-)

You can check the 947 LOC thing at http://­code.­google.­com/p/raspf (the Code tab).

If you do check it, jeep in mind the fol­low­ing:

  • It us­es a few lib­s, and they are in­­­clud­ed in the source code for sim­­plic­i­­ty.

  • I do some­­times com­mit code that does­n't com­pile

  • I do some­­times com­mit code that fails tests

  • You need cmake

  • I am not giv­ing a damn about mem­o­ry man­age­­ment right now, so don't both­­er wor­ry­ing about leak­s: ev­ery­thing leaks in this code. I want to make it func­­tion­al first, then I can plug it one func­­tion at a time (sim­­ply by run­n­ing the unit test­ing code with a mem­o­ry check­­er).

En­joy (although it's not pre­cise­ly en­joy­able code right now ;-)

C is not Python

I am port­ing pyspf to C (long sto­ry, and I am stupid for try­ing). But of course, C is not python.

So you don't have anything nearly as nice as re.­com­pile("what­ev­er").s­plit("­somestring").

What is that good for, you may ask? Well, to do things like split­ting email ad­dress­es while val­i­dat­ing them, or in this spe­cif­ic case, to val­i­date SPF mech­a­nisms (n­ev­er­mind what those are).

But hey, you can al­ways do this (ex­cuse me while I weep a lit­tle):

struct bstrList *re_split(const char *string, const char *pattern)
{
    int status;
    regex_t re;
    regmatch_t pmatch[20];

    if (regcomp(&re, pattern, REG_ICASE|REG_EXTENDED) != 0)
    {
        return(0);      /* Report error. */
    }

    bstring tmp=bfromcstr("");
    char *ptr=(char *)string;

    for (;;)
    {
        status = regexec(&re, ptr, (size_t)20, pmatch, 0);
        if (status==REG_NOMATCH)
        {
            break;
        }
        bcatblk (tmp,ptr,pmatch[0].rm_so);
        bconchar (tmp,0);
        bcatblk (tmp,ptr+pmatch[0].rm_so,pmatch[0].rm_eo-pmatch[0].rm_so);
        bconchar (tmp,0);
        ptr=ptr+pmatch[0].rm_eo;

    }
    regfree(&re);
    bcatblk (tmp,ptr,strlen(string)-(ptr-string));
    struct bstrList *l= bsplit(tmp,0);
    return l;
}

And that is prob­a­bly wrong for some cas­es (and it does­n't split the ex­act same way as Python, but that's what unit test­ing is for).

I must be miss­ing some­thing that makes reg­comp & friends nicer to use. Right? Right?

Itching.

Ok, the SPF im­ple­men­ta­tion sit­u­a­tion is kin­da pa­thet­ic.

There seems to be ex­act­ly one main­tained C im­ple­men­ta­tion. And it's win­dows-on­ly.

  • lib­spf's we­b­site seems to have dis­­ap­­peared

  • lib­spf2's not RFC-­­com­­pli­ant (ver­i­­fied for 1.2.5) and their is­­sue re­­port­ing sys­tem bounces.

So, I have tak­en the most com­pli­ant one I found whose code I can ac­tu­al­ly fol­low (that would be the python one) and am reim­ple­ment­ing it in C (us­ing bstr­lib and lib­d­jbdns).

It will prob­a­bly not come to a good end, but hey, it may work ;-)

There is one thing worse than not having a test suite

UP­DATE: There is *an­oth­er* *bet­ter* test suite It is in YAM­L, though, so I need to parse it be­fore I can use it, but that's my prob­lem.

It's hav­ing a test suite that makes no sense.

I have writ­ten, for my ra-­plu­g­ins project (y­ou don't have to know what it is for this post any­way) a piece of code that tries to check mail senders us­ing SPF.

SPF is an open stan­dard. It has stan­dard im­ple­men­ta­tion­s. It has a test suite (http://www.schlit­t.net/spf/test­s/).

The test suite says this:

spf­query -ip=192.0.2.1 -sender=05.spf1-test.­mail­zone.­com -h­elo=05.spf1-test.­mail­zone.­com re­sult /.*/ fail smt­p-­com­ment /.*/ ex­pla­na­tion head­er-­com­ment /.*/ spf­query: do­main of 05.spf1-test.­mail­zone.­com does not des­ig­nate 192.0.2.1 as per­mit­ted sender re­ceived-spf /.*/ Re­ceived-SPF: fail (spf­query: do­main of 05.spf1-test.­mail­zone.­com does not des­ig­nate 192.0.2.1 as per­mit­ted sender) clien­t-ip=192.0.2.1; en­velope-from=­post­mas­ter@05.spf1-test.­mail­zone.­com; helo=05.spf1-test.­mail­zone.­com;

So, yeah:

$ spfquery -ip=192.0.2.1 -sender=05.spf1-test.mailzone.com -helo=05.spf1-test.mailzone.com
fail
Please see http://www.openspf.org/why.html?sender=05.spf1-test.mailzone.com&ip=192.0.2.1&receiver=spfquery
spfquery: domain of 05.spf1-test.mailzone.com does not designate 192.0.2.1 as permitted sender
Received-SPF: fail (spfquery: domain of 05.spf1-test.mailzone.com does not designate
192.0.2.1 as permitted sender) client-ip=192.0.2.1;
envelope-from=05.spf1-test.mailzone.com; helo=05.spf1-test.mailzone.com;

So, the stan­dard im­ple­men­ta­tion does what the test suite says.

Too bad that, if you both­er check­ing the URL you are told to "please see"...

The do­main 05.spf1-test.­mail­zone.­com has pub­lished an SPF pol­i­cy, how­ev­er the pol­i­cy is neu­tral on whether 192.0.2.1 is au­tho­rized to send mail on its be­half.

Ei­ther both the test suite and the sam­ple im­ple­men­ta­tion are wrong, or the site is wrong. And I am lean­ing to­wards "the test suite is wrong", be­cause...

$ host -t txt 05.spf1-test.mailzone.com
05.spf1-test.mailzone.com descriptive text "v=spf1 default=deny"

If you check the record syn­tax (http://www.open­spf.org/SPF_Record_Syn­tax) de­fault is an un­known mod­i­fier, and should be ig­nored, so the record is sim­ply "v=spf1", and in­deed the re­sult is neu­tral and there is no rea­son why this should be a fail.


Contents © 2000-2023 Roberto Alsina