Libmail
-- Sebastian Pawlak, 2005.
Zaprezentowana funkcja umożliwia wysyłanie e-maila według protokołu SMTP. Obsługiwane jest logowanie typu: LOGIN, PLAIN i DIGEST-MD5. Do działania wymaga plików md5.c i md5.h dostępnych w internecie.
Kod źródłowy pliku "libmail.h":
/* libmail: biblioteka z funkcja wysylania e-maila SMTP (RFC821) * * Sebastian Pawlak, 2005. */ #ifndef _LIB_MAIL_H_ #define _LIB_MAIL_H_ #define DEBUG int sendEmail(const char *smtpIP, int port, const char *domain, const char *from, const char *to, const char *subject, const char *body, const char *auth, const char *login, const char *passwd); #endif
Kod źródłowy pliku "libmail.c":
/* libmail: biblioteka z funkcja wysylania e-maila SMTP (RFC821) * * Obsluguje logowanie: PLAIN, LOGIN, DIGEST-MD5 * * Wymaga plikow md5.c i md5.h autorstwa L. Peter Deutsch * * Sebastian Pawlak, 2005. */ #include <stdio.h> #include <stdlib.h> /* dla strtol() */ #include <fcntl.h> /* dla O_RDONLY */ #include <ctype.h> /* dla isdigit() */ #include <unistd.h> /* dla close() */ #include <arpa/inet.h> /* dla inet_pton() */ #include <sys/socket.h> /* dla socket(), recv(), connect(), AF_INET, ... */ #include <setjmp.h> /* dla sigsetjmp(), siglongjmp() */ #include <sys/time.h> /* dla setitimer(), ITIMER_REAL, struct itimerval */ #include <sys/wait.h> /* dla SIGALR */ #include <errno.h> #include "md5.h" #include "libmail.h" enum { STATE_WAIT_220 = 0, /* oczekiwanie na pierwszy pakiet od serwera */ STATE_SEND_HELO, /* wyslanie HELO */ STATE_WAIT_HELO_ANSWER, /* oczekiwanie na odpowiedz serwera na HELO */ STATE_SEND_AUTH, /* wyslanie AUTH */ STATE_WAIT_AUTH_ANSWER, /* oczekiwanie na odpowiedz serwera na AUTH */ STATE_SEND_LOGIN, /* wyslanie loginu */ STATE_WAIT_LOGIN_ANSWER, /* oczekiwanie na odpowiedz serwera na login */ STATE_SEND_PASSWD, /* wyslanie hasla */ STATE_WAIT_PASSWD_ANSWER, /* oczekiwanie na odpowiedz serwera na haslo */ STATE_SEND_MAIL, /* wyslanie MAIL */ STATE_WAIT_MAIL_ANSWER, /* oczekiwanie na odpowiedz serwera na MAIL */ STATE_SEND_RCPT, /* wyslanie RCPT */ STATE_WAIT_RCPT_ANSWER, /* oczekiwanie na odpowiedz serwera na PCPT */ STATE_SEND_DATA, /* wyslanie DATA */ STATE_WAIT_DATA_ANSWER, /* oczekiwanie na odpowiedz serwera na DATA */ STATE_SEND_DATA_BODY, /* wyslanie tresci e-maila */ STATE_WAIT_DATA_BODY_ANSWER, /* oczekiwanie na odpowiedz serwera na '.' */ STATE_SEND_QUIT, /* wyslanie QUIT */ STATE_WAIT_QUIT_ANSWER, /* oczekiwanie na odpowiedz serwera na QUIT */ MAX_LEN = 256, DEFAULT_CONNECT_TIMEOUT = 5, /* timeout wyslania e-maila */ }; void closeConnection(int sockfd, int state, const char *answer); int recvData(int sockfd, int *number, int *firstDigit, char *answer); int sendData(int sockfd, const char *data); int connectSlave(const char *ip, int port); void encodeBase64(const unsigned char *s, unsigned char *t, int len); void decodeBase64(const unsigned char *s, unsigned char *t, int len); int makeDigestMD5response(const char *s, char *t, const char *login, const char *passwd); static sigjmp_buf jmpBuf; static sig_atomic_t jmpOk = 0; /* connectTimeoutHandler: na potrzeby obslugi timeout'u dla funkcji sendEmail() */ static void connectTimeoutHandler(int sig) { if (jmpOk == 0) return; siglongjmp(jmpBuf, 1); } /* sendEmail: wysyla e-mail do serwera SMTP * umozliwia autoryzacje LOGIN, PLAIN, DIGEST-MD5 */ int sendEmail(const char *smtpIP, int port, const char *domain, const char *from, const char *to, const char *subject, const char *body, const char *auth, const char *login, const char *passwd) { struct sigaction act, oldact; struct itimerval timval; int sockfd, state, number, firstDigit; char s[1024]; char answer[MAX_LEN] = "\0"; int ret = 0; errno = 0; /* jesli e-mail nie zostanie przeslany w pewnym czasie, przerywamy */ jmpOk = 0; memset(&act, 0, sizeof (act)); act.sa_handler = &connectTimeoutHandler; sigaction(SIGALRM, &act, &oldact); timval.it_interval.tv_usec = 0; timval.it_interval.tv_sec = 0; timval.it_value.tv_usec = 0; timval.it_value.tv_sec = DEFAULT_CONNECT_TIMEOUT; setitimer(ITIMER_REAL, &timval, 0); if (sigsetjmp(jmpBuf, 1) == 0) { jmpOk = 1; /* tworzenie polaczenia */ if ((sockfd = connectSlave(smtpIP, port)) < 0) { fprintf(stderr,"Problem z nawiazaniem polaczenia " "z serwerem SMTP!\n"); ret = -1; } /* wysylanie e-maila, automat stanow */ state = STATE_WAIT_220; while (ret == 0) { #ifdef DEBUG fprintf(stdout, "stan: %d\n", state); #endif switch (state) { case STATE_WAIT_220: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 220) state = STATE_SEND_HELO; else if (number == 554) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_HELO: sprintf(s, "HELO %s\r\n", domain); if (sendData(sockfd, s) == 0) state = STATE_WAIT_HELO_ANSWER; break; case STATE_WAIT_HELO_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 250) state = STATE_SEND_AUTH; else if (firstDigit == 5) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_AUTH: if (auth[0] == '\0') { #ifdef DEBUG fprintf(stdout, "Bez logowania\n"); #endif state = STATE_SEND_MAIL; break; } if (!strcmp(auth, "PLAIN")) { char t[256]; sprintf(s, "%s %s %s", login, login, passwd); s[strlen(login)] = '\0'; s[2 * strlen(login) + 1] = '\0'; encodeBase64(s, t, 2 * strlen(login) + strlen(passwd) + 2); sprintf(s, "AUTH PLAIN %s\r\n", t); #ifdef DEBUG fprintf(stdout, "%s\n", s); #endif } else if (!strcmp(auth, "LOGIN")) sprintf(s, "AUTH %s\r\n", auth); else if (!strcmp(auth, "DIGEST-MD5")) { sprintf(s, "AUTH %s\r\n", auth); } if (sendData(sockfd, s) == 0) { if (!strcmp(auth, "PLAIN")) state = STATE_WAIT_PASSWD_ANSWER; else state = STATE_WAIT_AUTH_ANSWER; } break; case STATE_WAIT_AUTH_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 334) state = STATE_SEND_LOGIN; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } if (!strcmp(auth, "DIGEST-MD5")) { decodeBase64(answer + 4, s, strlen(answer)); #ifdef DEBUG fprintf(stdout, "zdekodowane: %s\n\n", s); #endif } break; case STATE_SEND_LOGIN: if (!strcmp(auth, "DIGEST-MD5")) { char t[1024]; decodeBase64(answer + 4, s, strlen(answer)); #ifdef DEBUG fprintf(stdout, "zdekodowane: %s\n\n", s); #endif makeDigestMD5response(s, t, login, passwd); encodeBase64(t, s, strlen(t)); sprintf(s, "%s\r\n", s); } else { encodeBase64(login, s, strlen(login)); strcat(s, "\r\n"); } if (sendData(sockfd, s) == 0) state = STATE_WAIT_LOGIN_ANSWER; break; case STATE_WAIT_LOGIN_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 334) state = STATE_SEND_PASSWD; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_PASSWD: /* DIGEST-MD5 wymaga przeslania czegos, po autoryzacji */ if (!strcmp(auth, "DIGEST-MD5")) strcpy(s, "\r\n"); else { encodeBase64(passwd, s, strlen(passwd)); strcat(s, "\r\n"); } if (sendData(sockfd, s) == 0) state = STATE_WAIT_PASSWD_ANSWER; break; case STATE_WAIT_PASSWD_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 235) state = STATE_SEND_MAIL; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_MAIL: sprintf(s, "MAIL FROM:<%s>\r\n", from); if (sendData(sockfd, s) == 0) state = STATE_WAIT_MAIL_ANSWER; break; case STATE_WAIT_MAIL_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 250) state = STATE_SEND_RCPT; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_RCPT: sprintf(s, "RCPT TO:<%s>\r\n", to); if (sendData(sockfd, s) == 0) state = STATE_WAIT_RCPT_ANSWER; break; case STATE_WAIT_RCPT_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if ((number == 250) || (number == 251)) state = STATE_SEND_DATA; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_DATA: if (sendData(sockfd, "DATA\r\n") == 0) state = STATE_WAIT_DATA_ANSWER; break; case STATE_WAIT_DATA_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 354) state = STATE_SEND_DATA_BODY; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_DATA_BODY: sprintf(s, "From: %s\r\nTo: %s\r\nSubject: " "%s\r\n%s\r\n.\r\n", from, to, subject, body); if (sendData(sockfd, s) == 0) state = STATE_WAIT_DATA_BODY_ANSWER; break; case STATE_WAIT_DATA_BODY_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 250) state = STATE_SEND_QUIT; else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; case STATE_SEND_QUIT: if (sendData(sockfd, "QUIT\r\n") == 0) state = STATE_WAIT_QUIT_ANSWER; break; case STATE_WAIT_QUIT_ANSWER: if (recvData(sockfd, &number, &firstDigit, answer) == 0) { if (number == 221) { closeConnection(sockfd, state, answer); ret = 1; /* e-mail zostal wyslany poprawnie */ } else if ((firstDigit == 5) || (firstDigit == 4)) { closeConnection(sockfd, state, answer); ret = -1; } } break; } } } else { errno = ETIMEDOUT; fprintf(stderr, "sendEmail() timeout!\n"); } timval.it_interval.tv_usec = 0; timval.it_interval.tv_sec = 0; timval.it_value.tv_usec = 0; timval.it_value.tv_sec = 0; setitimer(ITIMER_REAL, &timval, 0); sigaction(SIGALRM, &oldact, NULL); if (ret != 1) { fprintf(stderr, "Problem z wyslaniem e-maila!\n"); return -1; } else if (ret == 1) { #ifdef DEBUG fprintf(stdout, "E-mail wyslany poprawnie\n"); #endif return 0; } return -1; /* byl timeout */ } /* closeConnection: zamykanie polaczenia z serwerem SMTP */ void closeConnection(int sockfd, int state, const char *answer) { close(sockfd); #ifdef DEBUG fprintf(stdout, "Rozlaczenie (ostatni stan: %d, ostatnia odpowiedz: %s)\n", state, answer); #endif } /* recvData: odczytuje dane od serwera SMTP */ int recvData(int sockfd, int *number, int *firstDigit, char *answer) { int n; /* odczyt z sieci (odczyt blokujacy) */ if ((n = recv(sockfd, answer, MAX_LEN - 1, 0)) == -1) { fprintf(stderr, "Problem z odczytem danych z sieci!\n"); return -1; } if (n < 3) return -2; answer[n] = '\0'; #ifdef DEBUG fprintf(stdout, "odebrano: %s\n", answer); #endif /* obsluga odebranych danych */ if ((*number = strtol(answer, NULL, 10)) == 0) return -3; if (isdigit(answer[0])) *firstDigit = answer[0] - '0'; else return -4; return 0; } /* sendData: wysyla dane do serwera SMTP */ int sendData(int sockfd, const char *data) { #ifdef DEBUG fprintf(stdout, "wysylanie: %s\n", data); #endif /* wysylanie danych do serwera */ if (send(sockfd, data, strlen(data), 0) != strlen(data)) { fprintf(stderr, "Problem z wyslaniem danych do sieci!\n"); return -1; } return 0; } /* connectSlave: laczy sie z odleglym serwerem SMTP po TCP pod adresem "ip" i * na porcie "port" */ int connectSlave(const char *ip, int port) { struct sockaddr_in address; int sockfd; #ifdef DEBUG fprintf(stdout, "connectSlave(): IP: %s, port: %d.\n", ip, port); #endif /* tworzenie gniazda TCP */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "Problem z socket() w connectSlave()!\n"); return -1; } bzero(&address, sizeof (address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); /* IP */ address.sin_port = htons(port); /* port */ /* proby nawiazywania polaczenia z odleglym serwerem */ if (connect(sockfd, (struct sockaddr *)&address, sizeof (address)) == -1) { fprintf(stderr, "Problem z connect() w connectSlave()!\n"); return -2; } #ifdef DEBUG fprintf(stdout, "Polaczenie z odleglym serwerem zostalo nawiazane.\n"); #endif return sockfd; } const unsigned char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/"; /* encodeBlock: koduje blok trzech znakow na cztery znaki z tablicy base64 */ void encodeBlock(const unsigned char in[3], unsigned char out[4], int len) { out[0] = base64[in[0] >> 2]; out[1] = base64[((in[0] & 0x03) << 4) | (in[1] >> 4)]; out[2] = len > 1 ? base64[((in[1] & 0x0f) << 2) | (in[2] >> 6)] : '='; out[3] = len > 2 ? base64[in[2] & 0x3f] : '='; } /* encodeBase64: koduje zadany lancuch znakow na Base64 */ void encodeBase64(const unsigned char *s, unsigned char *t, int len) { int i; for (i = 0; i < len / 3; i++) encodeBlock(&s[i * 3], &t[i * 4], 3); if (len - i * 3 > 0) encodeBlock(&s[i * 3], &t[i * 4], len - i * 3), i++; t[i * 4] = '\0'; } /* decodeBlock: odkodowuje blok czterech znakow na trzy znaki */ void decodeBlock(const unsigned char in[4], unsigned char out[3]) { unsigned char inTmp[4] = { 0, 0, 0 }; int i; unsigned char *p; for (i = 0; i <= 3; i++) if ((p = strchr(base64, in[i])) != NULL) inTmp[i] = p - base64; out[0] = (inTmp[0] << 2) | (inTmp[1] >> 4); out[1] = (inTmp[1] << 4) | (inTmp[2] >> 2); out[2] = (inTmp[2] << 6) | (inTmp[3]); } /* decodeBase64: odkodowuje zadany lancuch z Base64 */ void decodeBase64(const unsigned char *s, unsigned char *t, int len) { int i; for (i = 0; i < len / 4; i++) decodeBlock(&s[i * 4], &t[i * 3]); /* "t" musi miec wielkosc rowna wielokrotnosci trojki + 1 */ t[i * 3] = '\0'; } /* extractToken: ekstraktuje wartosc dla podanego tokenu z lancucha znakow */ int extractToken(const char *s, const char *token, char *value, int valLen) { char *p = NULL, *q = NULL; if ((p = strstr(s, token)) != NULL) { p += strlen(token); /* += strlen("realm="); */ if (*p == '\"') { if ((q = strchr(p + 1, '\"')) == NULL) /* zamykajacy " */ return -1; strncpy(value, p + 1, q - p - 1 >= valLen ? valLen - 1 : q - p - 1); } else { if ((q = strchr(p, ',')) == NULL) /* przecinek */ q += strlen(p); strncpy(value, p, q - p >= valLen ? valLen - 1 : q - p); } } } /* toHex: konwertuje 16-bitowy ciag znakow na 32-bitowy ciag znakow hex */ void toHex(const unsigned char bin[16], unsigned char hex[33]) { int i; unsigned char c; for (i = 0; i < 32; i++) { if (i % 2 == 0) c = (bin[i / 2] >> 4) & 0x0f; else c = (bin[i / 2]) & 0x0f; if (c <= 9) hex[i] = c + '0'; else hex[i] = c + 'a' - 10; } hex[32] = '\0'; } /* makeDigestMD5response: tworzy lancuch z odpowiedzia dla serwera * podczas autoryzacji Digest-MD5 */ int makeDigestMD5response(const char *s, char *t, const char *login, const char *passwd) { char realm[256] = "\0", nonce[64] = "\0", cnonce[32] = "\0", qop[64] = "\0", uri[261] = "\0", w[32], *p = NULL, *q = NULL; int f; md5_state_t state; md5_byte_t digest[17]; char hexA1[33], hexA2[33], resp[33]; printf("%s\n", s); /* ekstrakcja pewnych wartosci z lancucha (digest-challenge, RFC2831) * otrzymanego od serwera * */ extractToken(s, "realm=", realm, sizeof (realm)); extractToken(s, "nonce=", nonce, sizeof (nonce)); extractToken(s, "qop=", qop, sizeof (qop)); sprintf(uri, "smtp/%s", realm); /* generowanie losowego "cnonce" */ if ((f = open("/dev/urandom", O_RDONLY)) == -1) return -1; if (read(f, w, 12) != 12) { close(f); return -2; } close(f); encodeBase64(w, cnonce, 12); cnonce[16] = '\0'; /* tworzenie pola "response" * * HEX( KD ( HEX(H(A1)), * { nonce-value, ":" nc-value, ":", * cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) * * A1 = { H( { username-value, ":", realm-value, ":", passwd } ), * ":", nonce-value, ":", cnonce-value } * * A2 = { "AUTHENTICATE:", digest-uri-value } * * If the "qop" value is "auth-int" or "auth-conf" then A2 is: * * A2 = { "AUTHENTICATE:", digest-uri-value, * ":00000000000000000000000000000000" } */ /* A1 */ md5_init(&state); md5_append(&state, (const md5_byte_t *)login, strlen(login)); md5_append(&state, ":", 1); md5_append(&state, (const md5_byte_t *)realm, strlen(realm)); md5_append(&state, ":", 1); md5_append(&state, (const md5_byte_t *)passwd, strlen(passwd)); md5_finish(&state, digest); md5_init(&state); md5_append(&state, digest, 16); md5_append(&state, ":", 1); md5_append(&state, nonce, strlen(nonce)); md5_append(&state, ":", 1); md5_append(&state, cnonce, strlen(cnonce)); md5_finish(&state, digest); toHex(digest, hexA1); #ifdef DEBUG fprintf(stdout, "A1: %s\n", hexA1); #endif /* A2 */ md5_init(&state); md5_append(&state, "AUTHENTICATE:", sizeof ("AUTHENTICATE:") - 1); md5_append(&state, uri, strlen(uri)); if (!strcmp(qop, "auth-int")) md5_append(&state, ":00000000000000000000000000000000", sizeof (":00000000000000000000000000000000") - 1); md5_finish(&state, digest); toHex(digest, hexA2); #ifdef DEBUG fprintf(stdout, "A2: %s\n", hexA2); #endif /* response */ md5_init(&state); md5_append(&state, hexA1, 32); md5_append(&state, ":", 1); md5_append(&state, nonce, strlen(nonce)); md5_append(&state, ":00000001:", sizeof (":00000001:") - 1); md5_append(&state, cnonce, strlen(cnonce)); md5_append(&state, ":", 1); md5_append(&state, qop, strlen(qop)); md5_append(&state, ":", 1); md5_append(&state, hexA2, 32); md5_finish(&state, digest); toHex(digest, resp); resp[32] = '\0'; #ifdef DEBUG fprintf(stdout, "Response: %s\n", resp); #endif /* tworzenie lancucha wyjsciowego (digest-response) */ sprintf(t, "charset=utf-8,username=\"%s\",realm=\"%s\",nonce=\"%s\"," "nc=00000001,cnonce=\"%s\",digest-uri=\"%s\",qop=\"%s\"," "response=%s", login, realm, nonce, cnonce, uri, qop, resp); #ifdef DEBUG fprintf(stdout, "response: %s\n", t); #endif return 0; }
Kod źródłowy pliku "main.c":
#include <stdio.h> #include "libmail.h" int main(void) { sendEmail("213.180.130.20", 25, "smtp.poczta.onet.pl", "s_pawlak@op.pl", "s_pawlak@op.pl", "libmail", "Test libmail.", "LOGIN", "s_pawlak@op.pl", "*********"); /* wstaw haslo zamiast gwiazdek */ return 0; }
Funkcja wyświetla komunikaty diagnostyczne.
A oto przykładowy wynik działania programu.
Kod źródłowy pliku "wynik":
connectSlave(): IP: 213.180.130.20, port: 25. Polaczenie z odleglym serwerem zostalo nawiazane. stan: 0 odebrano: 220 smtp.poczta.onet.pl ESMTP (7) our local time is now Wed, 14 Dec 2005 00:43:40 +0100 stan: 1 wysylanie: HELO smtp.poczta.onet.pl stan: 2 odebrano: 250 smtp.poczta.onet.pl expected "HELO pcXXX.warszawa.sdi.tpnet.pl" stan: 3 wysylanie: AUTH LOGIN stan: 4 odebrano: 334 VXNlcm5hbWU6 stan: 5 wysylanie: c19wYXdsYWtAb3AucGw= stan: 6 odebrano: 334 UGFzc3dvcmQ6 stan: 7 wysylanie: bWlaaXgyemlw stan: 8 odebrano: 235 Authentication successful. stan: 9 wysylanie: MAIL FROM:<s_pawlak@op.pl> stan: 10 odebrano: 250 2.1.0 Sender syntax Ok stan: 11 wysylanie: RCPT TO:<s_pawlak@op.pl> stan: 12 odebrano: 250 2.1.5 Recipient address syntax Ok; rcpt=<s_pawlak@op.pl> stan: 13 wysylanie: DATA stan: 14 odebrano: 354 Start mail input; end with <CRLF>.<CRLF> stan: 15 wysylanie: From: s_pawlak@op.pl To: s_pawlak@op.pl Subject: libmail Test libmail. . stan: 16 odebrano: 250 2.6.0 Message accepted. stan: 17 wysylanie: QUIT stan: 18 odebrano: 221 2.0.0 smtp.poczta.onet.pl Out Rozlaczenie (ostatni stan: 18, ostatnia odpowiedz: 221 2.0.0 smtp.poczta.onet.pl Out ) E-mail wyslany poprawnie