/*************************************************************************** CSmtpCLient.c gb.net.smtp component (c) 2000-2007 Benoit Minisini This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ***************************************************************************/ #define __CSMTPCLIENT_C #include "CSmtpClient.h" //#define DEBUG_ME static char *_tmp = NULL; typedef struct { char *constant; int value; } MIME_FIND; static MIME_FIND _types[] = { { "text", LIBSMTP_MIME_TEXT }, { "message", LIBSMTP_MIME_MESSAGE }, { "image", LIBSMTP_MIME_IMAGE }, { "audio", LIBSMTP_MIME_AUDIO }, { "video", LIBSMTP_MIME_VIDEO }, { "application", LIBSMTP_MIME_APPLICATION }, { "multipart", LIBSMTP_MIME_MULTIPART }, { "custom!!", LIBSMTP_MIME_CUSTOM }, { "plain", LIBSMTP_MIME_SUB_PLAIN }, { "html", LIBSMTP_MIME_SUB_HTML }, { "english", LIBSMTP_MIME_SUB_ENGLISH }, { "richtext", LIBSMTP_MIME_SUB_RICHTEXT }, { "rfc822", LIBSMTP_MIME_SUB_RFC822 }, { "partial", LIBSMTP_MIME_SUB_PARTIAL }, { "gif", LIBSMTP_MIME_SUB_GIF }, { "jpeg", LIBSMTP_MIME_SUB_JPG }, { "png", LIBSMTP_MIME_SUB_PNG }, { "tiff", LIBSMTP_MIME_SUB_TIFF }, { "x-ms-bmp", LIBSMTP_MIME_SUB_MS_BMP }, { "x-xbitmap", LIBSMTP_MIME_SUB_XBITMAP }, { "x-xpixmap", LIBSMTP_MIME_SUB_XPIXMAP }, { "x-portable-anymap", LIBSMTP_MIME_SUB_PORTABLE_ANYMAP }, { "x-portable-bitmap", LIBSMTP_MIME_SUB_PORTABLE_BITMAP }, { "x-portable-graymap", LIBSMTP_MIME_SUB_PORTABLE_GRAYMAP }, { "x-portable-pixmap", LIBSMTP_MIME_SUB_PORTABLE_PIXMAP }, { "mpeg", LIBSMTP_MIME_SUB_MPEGAUD }, { "midi", LIBSMTP_MIME_SUB_MIDI }, { "x-wav", LIBSMTP_MIME_SUB_WAV }, { "x-aiff", LIBSMTP_MIME_SUB_AIFF }, { "mpeg", LIBSMTP_MIME_SUB_MPEGVID }, { "x-ms-video", LIBSMTP_MIME_SUB_MSVIDEO }, { "quicktime", LIBSMTP_MIME_SUB_QUICKTIME }, { "fli", LIBSMTP_MIME_SUB_FLI }, { "rtf", LIBSMTP_MIME_SUB_RTF }, { "postscript", LIBSMTP_MIME_SUB_POSTSCRIPT }, { "pdf", LIBSMTP_MIME_SUB_PDF }, { "zip", LIBSMTP_MIME_SUB_ZIP }, { "x-debian-package", LIBSMTP_MIME_SUB_DEBIAN_PACKAGE }, { "x-executable", LIBSMTP_MIME_SUB_EXECUTABLE }, { "x-gtar", LIBSMTP_MIME_SUB_GTAR }, { "x-shellscript", LIBSMTP_MIME_SUB_SHELLSCRIPT }, { "x-tar", LIBSMTP_MIME_SUB_TAR }, { "octet-stream", LIBSMTP_MIME_SUB_OCTET_STREAM }, { "mixed", LIBSMTP_MIME_SUB_MIXED }, { "parallel", LIBSMTP_MIME_SUB_PARALLEL }, { "digest", LIBSMTP_MIME_SUB_DIGEST }, { "alternative", LIBSMTP_MIME_SUB_ALTERNATIVE }, { NULL, 0 } }; static MIME_FIND _charsets[] = { { "us-ascii", LIBSMTP_CHARSET_USASCII }, { "iso-8859-1", LIBSMTP_CHARSET_ISO8859_1 }, { "iso-8859-2", LIBSMTP_CHARSET_ISO8859_2 }, { "iso-8859-3", LIBSMTP_CHARSET_ISO8859_3 }, { "iso-8859-15", LIBSMTP_CHARSET_ISO8859_15 }, { "utf-8", LIBSMTP_CHARSET_UTF_8 }, { NULL, 0 } }; static char *get_address(char *address) { long len; GB.FreeString(&_tmp); if (!address || *address == 0) return ""; len = GB.StringLength(address); if (address[0] == '<' && address[len - 1] == '>') return address; GB.NewString(&_tmp, "<", 1); GB.AddString(&_tmp, address, len); GB.AddString(&_tmp, ">", 1); return _tmp; } static bool send_recipient(struct libsmtp_session_struct *session, GB_ARRAY rec, int type) { int i; if (!rec) return FALSE; for (i = 0; i < GB.Array.Count(rec); i++) { if (libsmtp_add_recipient(type, get_address(*(char **)GB.Array.Get(rec, i)), session)) return TRUE; } return FALSE; } static int find_constant(MIME_FIND *table, char *str, int len) { if (len <= 0) len = strlen(str); if (len >= 2 && str[0] == '"' && str[len -1] == '"') { str++; len--; } while (table->constant) { if (strncasecmp(table->constant, str, len) == 0) return table->value; table++; } return (-1); } static int decode_mime(char *mime, int *type, int *subtype, int *encoding, int *charset) { char *p, *p2; *charset = find_constant(_charsets, GB.System.Charset(), 0); if (!mime || !*mime) { *type = LIBSMTP_MIME_TEXT; *subtype = LIBSMTP_MIME_SUB_PLAIN; *encoding = LIBSMTP_ENC_QUOTED; return FALSE; } p = mime; p2 = index(p, '/'); if (!p2) { GB.Error("Cannot find MIME subtype"); return TRUE; } *type = find_constant(_types, p, p2 - p); if (*type < 0) { GB.Error("Unknown MIME type"); return TRUE; } p = p2 + 1; p2 = index(p, ';'); if (p2) { *subtype = find_constant(_types, p, p2 - p); p = p2 + 1; if (strncasecmp(p, "CHARSET=", 8)) { GB.Error("Syntax error in MIME charset"); return TRUE; } p += 8; *charset = find_constant(_charsets, p, &mime[strlen(mime)] - p); } else { *subtype = find_constant(_types, p, &mime[strlen(mime)] - p); } if (*subtype < 0) { GB.Error("Unknown MIME subtype"); return TRUE; } if (*type == LIBSMTP_MIME_TEXT || *type == LIBSMTP_MIME_MESSAGE) { *encoding = LIBSMTP_ENC_QUOTED; if (*charset < 0) { GB.Error("Unknown MIME charset"); return TRUE; } } else { *charset = LIBSMTP_CHARSET_NOCHARSET; if (*type == LIBSMTP_MIME_MULTIPART) *encoding = LIBSMTP_ENC_7BIT; else *encoding = LIBSMTP_ENC_BASE64; } return FALSE; } #if 0 static int old_add_part(CSMTPCLIENT *_object, char *mime, char *name, int length) { struct libsmtp_part_struct *part; struct libsmtp_part_struct *parent_part; int id; int type, subtype, encoding, charset; char buffer[24]; if (!THIS->main) { if (THIS->alternative) THIS->main = libsmtp_part_new(NULL, LIBSMTP_MIME_MULTIPART, LIBSMTP_MIME_SUB_ALTERNATIVE, LIBSMTP_ENC_7BIT, LIBSMTP_CHARSET_NOCHARSET, "MIME main part", -1, THIS->session); else THIS->main = libsmtp_part_new(NULL, LIBSMTP_MIME_MULTIPART, LIBSMTP_MIME_SUB_MIXED, LIBSMTP_ENC_7BIT, LIBSMTP_CHARSET_NOCHARSET, "MIME main part", -1, THIS->session); THIS->main->Tag = -1; THIS->parent = -1; } id = GB.Count(THIS->parts); if (THIS->parent < 0 || THIS->parent >= id) parent_part = THIS->main; else parent_part = THIS->parts[THIS->parent]; if (decode_mime(mime, &type, &subtype, &encoding, &charset)) return -1; if (!name || !*name) { sprintf(buffer, "MIME part #%d", id); name = buffer; } part = libsmtp_part_new(parent_part, type, subtype, encoding, charset, name, -1, THIS->session); if (!part) { GB.Error("Cannot add part: &1", libsmtp_strerr(THIS->session)); return -1; } part->Tag = id; #ifdef DEBUG_ME printf("part = %p parent_part = %p Tag = %d\n", part, parent_part, id); #endif *((struct libsmtp_part_struct **)GB.Add(&THIS->parts)) = part; return id; } #endif static bool begin_session(CSMTPCLIENT *_object) { int npart; CSMTPPART *p; struct libsmtp_part_struct *main_part; struct libsmtp_part_struct *part; int parent; struct libsmtp_part_struct *parent_part; int i; int type, subtype, encoding, charset; char buffer[24]; char *name; if (THIS->session) return TRUE; #ifdef DEBUG_ME fprintf(stderr, "begin_session\n"); #endif THIS->session = libsmtp_session_initialize(); npart = GB.Count(THIS->parts); if (npart == 0) { #ifdef DEBUG_ME fprintf(stderr, "Add void part\n"); #endif p = (CSMTPPART *)GB.Add(&THIS->parts); GB.NewString(&p->data, "\n", 1); npart = 1; } if (npart == 1) { p = &THIS->parts[0]; if (decode_mime(p->mime, &type, &subtype, &encoding, &charset)) return TRUE; name = p->name; if (!name || !*name) name = "MIME part"; parent_part = NULL; part = libsmtp_part_new(parent_part, type, subtype, encoding, charset, name, -1, THIS->session); if (!part) { GB.Error("Cannot add part: &1", libsmtp_strerr(THIS->session)); return TRUE; } part->Tag = 0; #ifdef DEBUG_ME fprintf(stderr, "part = %p parent_part = %p Tag = 0\n", part, parent_part); #endif p->part = part; } else { // Check mimetype for (i = 0; i < npart; i++) { if (decode_mime(THIS->parts[i].mime, &type, &subtype, &encoding, &charset)) return TRUE; } if (THIS->alternative) main_part = libsmtp_part_new(NULL, LIBSMTP_MIME_MULTIPART, LIBSMTP_MIME_SUB_ALTERNATIVE, LIBSMTP_ENC_7BIT, LIBSMTP_CHARSET_NOCHARSET, "MIME main part", -1, THIS->session); else main_part = libsmtp_part_new(NULL, LIBSMTP_MIME_MULTIPART, LIBSMTP_MIME_SUB_MIXED, LIBSMTP_ENC_7BIT, LIBSMTP_CHARSET_NOCHARSET, "MIME main part", -1, THIS->session); #ifdef DEBUG_ME fprintf(stderr, "main part = %p alternative = %d\n", main_part, THIS->alternative); #endif main_part->Tag = -1; parent = -1; for (i = 0; i < npart; i++) { p = &THIS->parts[i]; parent_part = main_part; decode_mime(p->mime, &type, &subtype, &encoding, &charset); name = p->name; if (!name || !*name) { sprintf(buffer, "MIME part #%d", i + 1); name = buffer; } part = libsmtp_part_new(parent_part, type, subtype, encoding, charset, name, -1, THIS->session); if (!part) { GB.Error("Cannot add part: &1", libsmtp_strerr(THIS->session)); return TRUE; } part->Tag = i; #ifdef DEBUG_ME fprintf(stderr, "part = %p parent_part = %p Tag = %d\n", part, parent_part, i); #endif p->part = part; } } return FALSE; } static void free_parts(CSMTPCLIENT *_object) { int i; CSMTPPART *part; #ifdef DEBUG_ME fprintf(stderr, "free_parts\n"); #endif for (i = 0; i < GB.Count(THIS->parts); i++) { part = &THIS->parts[i]; GB.FreeString(&part->name); GB.FreeString(&part->mime); GB.FreeString(&part->data); } GB.FreeArray(&THIS->parts); GB.NewArray(&THIS->parts, sizeof(CSMTPPART), 0); } static void end_session(CSMTPCLIENT *_object) { if (!THIS->session) return; #ifdef DEBUG_ME fprintf(stderr, "end_session\n"); #endif libsmtp_close(THIS->session); libsmtp_free(THIS->session); THIS->session = NULL; } BEGIN_METHOD_VOID(CSMTPCLIENT_new) GB.Array.New(&THIS->to, GB_T_STRING, 0); GB.Ref(THIS->to); GB.Array.New(&THIS->cc, GB_T_STRING, 0); GB.Ref(THIS->cc); GB.Array.New(&THIS->bcc, GB_T_STRING, 0); GB.Ref(THIS->bcc); GB.NewArray(&THIS->parts, sizeof(CSMTPPART), 0); END_METHOD BEGIN_METHOD_VOID(CSMTPCLIENT_free) end_session(THIS); free_parts(THIS); GB.FreeString(&THIS->host); GB.FreeString(&THIS->from); GB.FreeString(&THIS->subject); GB.Unref((void **)&THIS->to); GB.Unref((void **)&THIS->cc); GB.Unref((void **)&THIS->bcc); GB.FreeArray(&THIS->parts); GB.FreeString(&_tmp); END_METHOD BEGIN_METHOD_VOID(CSMTPCLIENT_send) struct libsmtp_session_struct *session; int i; const char *error; const char *where; char *addr; char buf[8]; addr = get_address(THIS->from); if (*addr == 0) { GB.Error("The From property must be set"); return; } if (begin_session(THIS)) { end_session(THIS); return; } session = THIS->session; libsmtp_set_environment(addr, THIS->subject, 0, session); if (send_recipient(session, THIS->to, LIBSMTP_REC_TO)) { where = "setting TO recipient"; goto __ERROR; } if (send_recipient(session, THIS->cc, LIBSMTP_REC_CC)) { where = "setting CC recipient"; goto __ERROR; } if (send_recipient(session, THIS->bcc, LIBSMTP_REC_BCC)) { where = "setting BCC recipient"; goto __ERROR; } if (libsmtp_connect(THIS->host ? THIS->host : "localhost", THIS->port, 0, session)) { where = "connecting to SMTP server"; goto __ERROR; } if (libsmtp_dialogue(session)) { where = "starting dialog"; goto __ERROR; } if (libsmtp_headers(session)) { where = "sending headers"; goto __ERROR; } if (libsmtp_mime_headers(session)) { where = "sending mime headers"; goto __ERROR; } //if (libsmtp_body_send_raw((char *)test, strlen(test), session)) goto __ERROR; if (GB.Count(THIS->parts)) { #ifdef DEBUG_ME fprintf(stderr, "**** Send parts\n"); #endif for (;;) { #ifdef DEBUG_ME fprintf(stderr, ">>>> libsmtp_part_next\n"); #endif if (libsmtp_part_next(session)) { if (libsmtp_errno(session)) { where = "reading next part"; goto __ERROR; } else break; } #ifdef DEBUG_ME fprintf(stderr, "<<<< libsmtp_part_next\n"); #endif i = libsmtp_part_query(session)->Tag; #ifdef DEBUG_ME fprintf(stderr, "**** part = %p Tag = %d\n", libsmtp_part_query(session), i); #endif if (THIS->parts[i].data) { if (libsmtp_part_send(THIS->parts[i].data, GB.StringLength(THIS->parts[i].data), session)) { where = "sending part"; goto __ERROR; } } else { if (libsmtp_part_send("", 0, session)) { where = "sending part"; goto __ERROR; } } } } if (libsmtp_body_end(session)) { where = "ending dialog"; goto __ERROR; } if (libsmtp_quit(session)) { where = "closing connection"; goto __ERROR; } end_session(THIS); free_parts(THIS); return; __ERROR: sprintf(buf, "%d", session->LastResponseCode); error = libsmtp_strerr(session); //if (!error || !*error) // error = "Unknown error"; GB.Error("&1 while &2 (SMTP error code #&3)", error, where, buf); end_session(THIS); END_METHOD #define IMPLEMENT_STRING_PROPERTY(_property) \ BEGIN_PROPERTY(CSMTPCLIENT_##_property) \ \ if (READ_PROPERTY) \ GB.ReturnString(THIS->_property); \ else \ GB.StoreString(PROP(GB_STRING), &THIS->_property); \ \ END_PROPERTY IMPLEMENT_STRING_PROPERTY(host); IMPLEMENT_STRING_PROPERTY(from); IMPLEMENT_STRING_PROPERTY(subject); BEGIN_PROPERTY(CSMTPCLIENT_port) if (READ_PROPERTY) GB.ReturnInteger(THIS->port); else THIS->port = VPROP(GB_INTEGER); END_PROPERTY #define IMPLEMENT_RECIPIENT(_recipient) \ BEGIN_PROPERTY(CSMTPCLIENT_##_recipient) \ \ GB.ReturnObject(THIS->_recipient); \ \ END_PROPERTY IMPLEMENT_RECIPIENT(to); IMPLEMENT_RECIPIENT(cc); IMPLEMENT_RECIPIENT(bcc); BEGIN_METHOD(CSMTPCLIENT_add, GB_STRING data; GB_STRING mime; GB_STRING name) CSMTPPART *part = (CSMTPPART *)GB.Add(&THIS->parts); if (!MISSING(name)) GB.StoreString(ARG(name), &part->name); if (!MISSING(mime)) GB.StoreString(ARG(mime), &part->mime); GB.StoreString(ARG(data), &part->data); END_METHOD BEGIN_PROPERTY(CSMTPCLIENT_alternative) if (READ_PROPERTY) GB.ReturnBoolean(THIS->alternative); else THIS->alternative = VPROP(GB_BOOLEAN); END_PROPERTY BEGIN_PROPERTY(CSMTPCLIENT_count) GB.ReturnInteger(GB.Count(THIS->parts)); END_PROPERTY GB_DESC CSmtpClientDesc[] = { GB_DECLARE("SmtpClient", sizeof(CSMTPCLIENT)), GB_METHOD("_new", NULL, CSMTPCLIENT_new, NULL), GB_METHOD("_free", NULL, CSMTPCLIENT_free, NULL), GB_PROPERTY("Host", "s", CSMTPCLIENT_host), GB_PROPERTY("Port", "i", CSMTPCLIENT_port), GB_PROPERTY("From", "s", CSMTPCLIENT_from), GB_PROPERTY("Subject", "s", CSMTPCLIENT_subject), GB_PROPERTY_READ("To", "String[]", CSMTPCLIENT_to), GB_PROPERTY_READ("Cc", "String[]", CSMTPCLIENT_cc), GB_PROPERTY_READ("Bcc", "String[]", CSMTPCLIENT_bcc), GB_PROPERTY("Alternative", "b", CSMTPCLIENT_alternative), GB_METHOD("Add", NULL, CSMTPCLIENT_add, "(Data)s[(MimeType)s(Name)s]"), GB_PROPERTY_READ("Count", "i", CSMTPCLIENT_count), GB_METHOD("Send", NULL, CSMTPCLIENT_send, NULL), GB_CONSTANT("_Properties", "s", "Host,Port"), GB_END_DECLARE };