8.22 Confirming Requests via Email
8.22.1 Problem
You want to allow users to
confirm a request via email while preventing third parties from
spoofing or falsifying confirmations.
8.22.2 Solution
Generate a random identifier, associate it
with the email address to be confirmed, and save it for verification
later. Send an email that contains the random identifier, along with
instructions for responding to confirm receipt and approval. If a
response is received, compare the identifier in the response with the
saved identifier for the email address from which the response was
received. If the identifiers don't match, ignore the
response and do nothing; otherwise, the confirmation was successful.
8.22.3 Discussion
The most common use for confirmation requests is to ensure that an
email address actually belongs to the person requesting membership on
some kind of mass mailing list (whether it's a
mailing list, newsletter, or some other type of mass mailing).
Joining a mass mailing list typically involves either sending mail to
an automated recipient or filling out a form on a web page.
The problem with this approach is that it is trivial for someone to
register someone else's email address with a mailing
list. For example, suppose that Alice wants to annoy Bob. If mailing
lists accepted email addresses without any kind of confirmation,
Alice could register Bob's email address with as
many mailing lists as she could find. Suddenly, Bob would begin
receiving large amounts of email from mailing lists with which he did
not register. In extreme cases, this could lead to denial of service
because Bob's mailbox could fill up with unwanted
email, or if Bob has a slow network connection, it could take an
unreasonable amount of time for him to download his email.
The solution to this problem is to confirm with Bob that he really
made the requests for membership with the mailing lists. When a
request for membership is sent for a mailing list, the mailing list
software can send an email to the address for which membership was
requested. This email will ask the recipient to respond with a
confirmation that membership is truly desired.
The simplest form of such a confirmation request is to require the
recipient to reply with an email containing some nonunique content,
such as the word "subscribe" or
something similar. This method is easiest for the mailing list
software to deal with because it does not have to keep any
information about what requests have been made or confirmed. It
simply needs to respond to confirmation responses by adding the
sender's email address to the mailing list roster.
Unfortunately, this is not an acceptable solution either, because
Alice might know what response needs to be sent back to the
confirmation request in order for the mailing list software to add
Bob to its roster. If Alice knows what needs to be sent, she can
easily forge a response email, making it appear to the mailing list
software as if it came from Bob's email address.
Sending a confirmation request that requires an affirmative
acknowledgement is a step in the right direction, but as we have just
described it, it is not enough. Instead of requiring a nonunique
acknowledgment, the confirmation request should contain a unique
identifier that is generated at the time that the request for
membership is made. To confirm the request, the recipient must send
back a response that also contains the same unique identifier.
Because a unique identifier is used, it is not possible for Alice to
know what she would need to send back to the mailing list software to
get Bob's email address on the roster, unless she
somehow had access to Bob's email. That would allow
her to see the confirmation request and the unique identifier that it
contains. Unfortunately, this is a much more difficult problem to
solve, and it is one that cannot be easily solved in software, so we
will not give it any further consideration.
To implement such a scheme, the mailing list software must maintain
some state information. In particular, upon receipt of a request for
membership, the software needs to generate the unique identifier to
include in the confirmation requests, and it must store that
identifier along with the email address for which membership has been
requested. In addition, it is a good idea to maintain some kind of a
timestamp so that confirmation requests will eventually expire.
Expiring confirmation requests significantly reduces the likelihood
that Alice can guess the unique identifier; more importantly, it also
helps to reduce the amount of information that must be remembered to
be able to confirm requests.
We define two functions in this recipe that provide the basic
implementation for the confirmation request scheme we have just
described. The first, spc_confirmation_create(
), creates a new confirmation request by
generating a unique identifier and storing it with the email address
for which confirmation is to be requested. It stores the confirmation
request information in an in-memory list of pending confirmations,
implemented simply as a dynamically allocated array. For use in a
production environment, a hash table or binary tree might be a better
solution for an in-memory data structure. Alternatively, the
information could be stored in a database.
The function spc_confirmation_create( )
(SpcConfirmationCreate() on Windows) will return 0
if some kind of error occurs. Possible errors include memory
allocation failures and attempts to add an address to the list of
pending confirmations that already exists in the list. If the
operation is successful, the return value will be 1. Two arguments
are required by spc_confirmation_create( ):
- address
-
Email address that is to be confirmed.
- id
-
Pointer to a buffer that will be allocated by
spc_confirmation_create( ). If the function
returns successfully, the buffer will contain the unique identifier
to send as part of the confirmation request email. It is the
responsibility of the caller to free the buffer using free(
) on Unix or LocalFree( ) on Windows.
You may adjust the SPC_CONFIRMATION_EXPIRE macro
from the default presented here. It controls how long pending
confirmation requests will be honored and is specified in seconds.
Note that the code we are presenting here does not send or receive
email at all. Programmatically sending and receiving email is outside
the scope of this book.
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Confirmation receipts must be received within one hour (3600 seconds) */
#define SPC_CONFIRMATION_EXPIRE 3600
typedef struct {
char *address;
char *id;
time_t expire;
} spc_confirmation_t;
static unsigned long confirmation_count, confirmation_size;
static spc_confirmation_t *confirmations;
static int new_confirmation(const char *address, const char *id) {
unsigned long i;
spc_confirmation_t *tmp;
/* first make sure that the address isn't already in the list */
for (i = 0; i < confirmation_count; i++)
if (!strcmp(confirmations[i].address, address)) return 0;
if (confirmation_count = = confirmation_size) {
tmp = (spc_confirmation_t *)realloc(confirmations,
sizeof(spc_confirmation_t) * (confirmation_size + 1));
if (!tmp) return 0;
confirmations = tmp;
confirmation_size++;
}
confirmations[confirmation_count].address = strdup(address);
confirmations[confirmation_count].id = strdup(id);
confirmations[confirmation_count].expire = time(0) + SPC_CONFIRMATION_EXPIRE;
if (!confirmations[confirmation_count].address ||
!confirmations[confirmation_count].id) {
if (confirmations[confirmation_count].address)
free(confirmations[confirmation_count].address);
if (confirmations[confirmation_count].id)
free(confirmations[confirmation_count].id);
return 0;
}
confirmation_count++;
return 1;
}
int spc_confirmation_create(const char *address, char **id) {
unsigned char buf[16];
if (!spc_rand(buf, sizeof(buf))) return 0;
if (!(*id = (char *)spc_base64_encode(buf, sizeof(buf), 0))) return 0;
if (!new_confirmation(address, *id)) {
free(*id);
return 0;
}
return 1;
}
Upon receipt of a response to a confirmation request, the address
from which it was sent and the unique identified contained within it
should be passed as arguments to spc_confirmation_receive(
) (SpcConfirmationReceive()
on Windows). If the address and unique identifier are in the list of
pending requests, the return from this function will be 1; otherwise,
it will be 0. Before the list is checked, expired entries will
automatically be removed.
int spc_confirmation_receive(const char *address, const char *id) {
time_t now;
unsigned long i;
/* Before we check the pending list of confirmations, prune the list to
* remove expired entries.
*/
now = time(0);
for (i = 0; i < confirmation_count; i++) {
if (confirmations[i].expire <= now) {
free(confirmations[i].address);
free(confirmations[i].id);
if (confirmation_count > 1 && i < confirmation_count - 1)
confirmations[i] = confirmations[confirmation_count - 1];
i--;
confirmation_count--;
}
}
for (i = 0; i < confirmation_count; i++) {
if (!strcmp(confirmations[i].address, address)) {
if (strcmp(confirmations[i].id, id) != 0) return 0;
free(confirmations[i].address);
free(confirmations[i].id);
if (confirmation_count > 1 && i < confirmation_count - 1)
confirmations[i] = confirmations[confirmation_count - 1];
confirmation_count--;
return 1;
}
}
return 0;
}
The
Windows
versions of spc_confirmation_create( ) and
spc_confirmation_receive( ) are named
SpcConfirmationCreate( ) and
SpcConfirmationReceive( ), respectively. The
arguments and return values for each are the same; however, there are
enough subtle differences in the underlying implementation that we
present an entirely separate code listing for Windows instead of
using the preprocessor to have a single version.
#include <windows.h>
/* Confirmation receipts must be received within one hour (3600 seconds) */
#define SPC_CONFIRMATION_EXPIRE 3600
typedef struct {
LPTSTR lpszAddress;
LPSTR lpszID;
LARGE_INTEGER liExpire;
} SPC_CONFIRMATION;
static DWORD dwConfirmationCount, dwConfirmationSize;
static SPC_CONFIRMATION *pConfirmations;
static BOOL NewConfirmation(LPCTSTR lpszAddress, LPCSTR lpszID) {
DWORD dwIndex;
LARGE_INTEGER liExpire;
SPC_CONFIRMATION *pTemp;
/* first make sure that the address isn't already in the list */
for (dwIndex = 0; dwIndex < dwConfirmationCount; dwIndex++) {
if (CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
pConfirmations[dwIndex].lpszAddress, -1,
lpszAddress, -1) = = CSTR_EQUAL) return FALSE;
}
if (dwConfirmationCount = = dwConfirmationSize) {
if (!pConfirmations)
pTemp = (SPC_CONFIRMATION *)LocalAlloc(LMEM_FIXED, sizeof(SPC_CONFIRMATION));
else
pTemp = (SPC_CONFIRMATION *)LocalReAlloc(pConfirmations,
sizeof(SPC_CONFIRMATION) * (dwConfirmationSize + 1), 0);
if (!pTemp) return FALSE;
pConfirmations = pTemp;
dwConfirmationSize++;
}
pConfirmations[dwConfirmationCount].lpszAddress = (LPTSTR)LocalAlloc(
LMEM_FIXED, sizeof(TCHAR) * (lstrlen(lpszAddress) + 1));
if (!pConfirmations[dwConfirmationCount].lpszAddress) return FALSE;
lstrcpy(pConfirmations[dwConfirmationCount].lpszAddress, lpszAddress);
pConfirmations[dwConfirmationCount].lpszID = (LPSTR)LocalAlloc(LMEM_FIXED,
lstrlenA(lpszID) + 1);
if (!pConfirmations[dwConfirmationCount].lpszID) {
LocalFree(pConfirmations[dwConfirmationCount].lpszAddress);
return FALSE;
}
lstrcpyA(pConfirmations[dwConfirmationCount].lpszID, lpszID);
/* File Times are 100-nanosecond intervals since January 1, 1601 */
GetSystemTimeAsFileTime((LPFILETIME)&liExpire);
liExpire.QuadPart += (SPC_CONFIRMATION_EXPIRE * (__int64)10000000);
pConfirmations[dwConfirmationCount].liExpire = liExpire;
dwConfirmationCount++;
return TRUE;
}
BOOL SpcConfirmationCreate(LPCTSTR lpszAddress, LPSTR *lpszID) {
BYTE pbBuffer[16];
if (!spc_rand(pbBuffer, sizeof(pbBuffer))) return FALSE;
if (!(*lpszID = (LPSTR)spc_base64_encode(pbBuffer, sizeof(pbBuffer), 0)))
return FALSE;
if (!NewConfirmation(lpszAddress, *lpszID)) {
LocalFree(*lpszID);
return FALSE;
}
return TRUE;
}
BOOL SpcConfirmationReceive(LPCTSTR lpszAddress, LPCSTR lpszID) {
DWORD dwIndex;
LARGE_INTEGER liNow;
/* Before we check the pending list of confirmations, prune the list to
* remove expired entries.
*/
GetSystemTimeAsFileTime((LPFILETIME)&liNow);
for (dwIndex = 0; dwIndex < dwConfirmationCount; dwIndex++) {
if (pConfirmations[dwIndex].liExpire.QuadPart <= liNow.QuadPart) {
LocalFree(pConfirmations[dwIndex].lpszAddress);
LocalFree(pConfirmations[dwIndex].lpszID);
if (dwConfirmationCount > 1 && dwIndex < dwConfirmationCount - 1)
pConfirmations[dwIndex] = pConfirmations[dwConfirmationCount - 1];
dwIndex--;
dwConfirmationCount--;
}
}
for (dwIndex = 0; dwIndex < dwConfirmationCount; dwIndex++) {
if (CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
pConfirmations[dwIndex].lpszAddress, -1,
lpszAddress, -1) = = CSTR_EQUAL) {
if (lstrcmpA(pConfirmations[dwIndex].lpszID, lpszID) != 0) return FALSE;
LocalFree(pConfirmations[dwIndex].lpszAddress);
LocalFree(pConfirmations[dwIndex].lpszID);
if (dwConfirmationCount > 1 && dwIndex < dwConfirmationCount - 1)
pConfirmations[dwIndex] = pConfirmations[dwConfirmationCount - 1];
dwConfirmationCount--;
return TRUE;
}
}
return FALSE;
}
8.22.4 See Also
Recipe 11.2
|