-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
qmail-remote: add infrastructure for EHLO parsing
Implementing extensions for qmail-remote often requires the usage of EHLO and checking if the server supports the extension. Every patch therefore usually comes with it's own version of EHLO parsing. Add yet another one that the patches can easily hook into. For the moment it will just send mails using ESMTP instead of SMTP for basically all cases, but not use any of the new information.
- Loading branch information
Showing
7 changed files
with
319 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ case_lowerb.o | |
case_lowers.o | ||
case_starts.o | ||
case.a | ||
ehlo_parse.o | ||
getln.o | ||
getln2.o | ||
getln.a | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#include "ehlo_parse.h" | ||
|
||
#include "case.h" | ||
#include "str.h" | ||
|
||
unsigned int ehlo_parse(const stralloc *smtptext, const struct smtpext *callbacks, unsigned int count) | ||
{ | ||
/* if this is a one line answer there will be no extensions */ | ||
if (smtptext->s[4] == ' ') | ||
return 0; | ||
|
||
size_t search = 0; | ||
unsigned int extensions = 0; | ||
const unsigned int maxmask = (1 << count) - 1; | ||
|
||
/* go through all lines of the multi line answer until we found all | ||
known extensions or we reach the last line */ | ||
do { | ||
unsigned int i; | ||
|
||
/* set search to the index of the next extension in the answer: | ||
it's always 5 characters after the '\n' (the other 4 are | ||
normally "250-") */ | ||
search += 5 + str_chr(smtptext->s + search, '\n'); | ||
|
||
for (unsigned int i = 0; i < count; i++) { | ||
const size_t elen = strlen(callbacks[i].name); | ||
if (!case_diffb(smtptext->s + search, elen, callbacks[i].name)) { | ||
if (smtptext->s[search + elen] == '\n' || | ||
smtptext->s[search + elen] == ' ') { | ||
if (callbacks[i].callback) { | ||
if (callbacks[i].callback(smtptext->s + search, str_chr(smtptext->s + search, '\n'))) | ||
extensions |= (1 << i); | ||
} else { | ||
extensions |= (1 << i); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
/* all known extensions found, no need to search any longer */ | ||
if (extensions == maxmask) | ||
break; | ||
} while (smtptext->s[search - 1] == '-'); | ||
|
||
return extensions; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#ifndef EHLO_PARSE_H | ||
#define EHLO_PARSE_H | ||
|
||
#include "stralloc.h" | ||
|
||
#include <sys/types.h> | ||
|
||
/** | ||
* Callbacks for EHLO response parsing | ||
*/ | ||
struct smtpext { | ||
const char *name; /**< name of the EHLO string */ | ||
/** | ||
* callback in case a space character follows name in the EHLO response | ||
* The function is given the current extension line without the leading 250- | ||
* and the length of the remainder, not including the trailing newline. This | ||
* includes the name part of the line so one could reuse the same callback | ||
* for multiple extensions. | ||
* | ||
* The callback shall return 0 if the line was ignored, or 1 if it was | ||
* accepted. | ||
* | ||
* In case the callback is NULL the extension is automatically accepted if | ||
* the name is matched and either followed by a space or newline. | ||
*/ | ||
int (*callback)(const char *ext, size_t extlen); | ||
}; | ||
|
||
/** | ||
* @brief parse the EHLO replies | ||
* @param smtptext the reply to parse | ||
* @param callbacks the list of callbacks | ||
* @param count number of entries in callbacks | ||
* @return mask of the matched callbacks | ||
* | ||
* Will parse all callbacks until either all lines are processed or all | ||
* callbacks have been matched. | ||
* | ||
* The return value will have the bit positions set according to the | ||
* entries in the callbacks param. | ||
*/ | ||
unsigned int ehlo_parse(const stralloc *smtptext, const struct smtpext *callbacks, unsigned int count); | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#include <check.h> | ||
|
||
#include "ehlo_parse.h" | ||
#include "stralloc.h" | ||
|
||
static int bad_call(const char *ext, size_t extlen) | ||
{ | ||
(void)ext; | ||
(void)extlen; | ||
ck_abort(); | ||
return 0; | ||
} | ||
|
||
static const struct smtpext bad_call_entry = { | ||
"server.example.org", bad_call | ||
}; | ||
|
||
START_TEST(test_ehlo_noext) | ||
{ | ||
stralloc thingy = { 0 }; | ||
unsigned int exts; | ||
thingy.s = (char*) "250 server.example.org\n"; | ||
thingy.len = strlen(thingy.s); | ||
|
||
exts = ehlo_parse(&thingy, &bad_call_entry, 1); | ||
|
||
ck_assert_uint_eq(exts, 0); | ||
} | ||
END_TEST | ||
|
||
START_TEST(test_ehlo_noparams) | ||
{ | ||
stralloc thingy = { 0 }; | ||
unsigned int exts; | ||
thingy.s = (char*) "250-server.example.org\n" | ||
"250-THEGOOD 1\n" | ||
"250-THEBAD1\n" | ||
"250 THEUGLY1\n"; | ||
thingy.len = strlen(thingy.s); | ||
|
||
const struct smtpext callbacks[] = { | ||
bad_call_entry, | ||
{ "THEGOOD", NULL }, | ||
{ "THEBAD", NULL }, | ||
{ "THEUGLY1", NULL } | ||
}; | ||
|
||
exts = ehlo_parse(&thingy, callbacks, 4); | ||
|
||
ck_assert_uint_eq(exts, 8 | 2); | ||
} | ||
END_TEST | ||
|
||
static int only_called_once(const char *ext, size_t extlen) | ||
{ | ||
static int guard; | ||
(void)ext; | ||
ck_assert_uint_eq(extlen, strlen("THEGOOD")); | ||
(void)extlen; | ||
if (guard++) | ||
ck_abort(); | ||
return 1; | ||
} | ||
|
||
START_TEST(test_ehlo_nodupes) | ||
{ | ||
stralloc thingy = { 0 }; | ||
unsigned int exts; | ||
thingy.s = (char*) "250-server.example.org\n" | ||
"250-THEGOOD\n" | ||
"250-THEGOOD\n" | ||
"250 THEGOOD\n"; | ||
thingy.len = strlen(thingy.s); | ||
|
||
const struct smtpext callback = { | ||
"THEGOOD", only_called_once | ||
}; | ||
|
||
exts = ehlo_parse(&thingy, &callback, 1); | ||
|
||
ck_assert_uint_eq(exts, 1); | ||
} | ||
END_TEST | ||
|
||
static int param_verifier(const char *ext, size_t extlen) | ||
{ | ||
static int guard; | ||
const char *params[] = { | ||
"THEGOOD a", | ||
"THEGOOD", | ||
"THEGOOD a b", | ||
"FINAL 1" | ||
}; | ||
char buf[32]; | ||
|
||
ck_assert_uint_eq(extlen, strlen(params[guard])); | ||
strncpy(buf, ext, extlen); | ||
buf[extlen] = '\0'; | ||
ck_assert_str_eq(buf, params[guard]); | ||
guard++; | ||
|
||
return guard == 4 ? 1 : 0; | ||
} | ||
|
||
START_TEST(test_ehlo_params) | ||
{ | ||
stralloc thingy = { 0 }; | ||
unsigned int exts; | ||
thingy.s = (char*) "250-server.example.org\n" | ||
"250-THEGOOD a\n" | ||
"250-THEGOOD\n" | ||
"250-THEGOOD a b\n" | ||
"250 FINAL 1"; | ||
thingy.len = strlen(thingy.s); | ||
|
||
const struct smtpext callbacks[] = { | ||
{ "THEGOOD", param_verifier }, | ||
{ "FINAL", param_verifier } | ||
}; | ||
|
||
exts = ehlo_parse(&thingy, callbacks, 2); | ||
|
||
ck_assert_uint_eq(exts, 2); | ||
} | ||
END_TEST | ||
|
||
TCase | ||
*ehlo_parse_checks(void) | ||
{ | ||
TCase *tc = tcase_create("basic operations"); | ||
|
||
tcase_add_test(tc, test_ehlo_noext); | ||
tcase_add_test(tc, test_ehlo_noparams); | ||
tcase_add_test(tc, test_ehlo_nodupes); | ||
tcase_add_test(tc, test_ehlo_params); | ||
|
||
return tc; | ||
} | ||
|
||
Suite | ||
*stralloc_suite(void) | ||
{ | ||
Suite *s = suite_create("notqmail ehlo_parse"); | ||
|
||
suite_add_tcase(s, ehlo_parse_checks()); | ||
|
||
return s; | ||
} | ||
|
||
int | ||
main(void) | ||
{ | ||
int number_failed; | ||
|
||
SRunner *sr = srunner_create(stralloc_suite()); | ||
srunner_run_all(sr, CK_NORMAL); | ||
number_failed = srunner_ntests_failed(sr); | ||
srunner_free(sr); | ||
|
||
return number_failed; | ||
} |