diff options
Diffstat (limited to 'src/mail')
-rw-r--r-- | src/mail/Makefile | 9 | ||||
-rw-r--r-- | src/mail/mail.vala | 209 | ||||
-rw-r--r-- | src/mail/mailer-interface.vala | 60 | ||||
-rw-r--r-- | src/mail/mailer.vala | 160 | ||||
-rw-r--r-- | src/mail/main.vala | 46 |
5 files changed, 484 insertions, 0 deletions
diff --git a/src/mail/Makefile b/src/mail/Makefile new file mode 100644 index 0000000..5250e62 --- /dev/null +++ b/src/mail/Makefile @@ -0,0 +1,9 @@ +all: mailer + +mailer: main.vala mailer.vala mail.vala mailer-interface.vala ../config/config-interface.vala + valac -o $@ --vapidir=../../vapi --pkg posix --pkg libesmtp --pkg gio-2.0 --pkg gmime-2.6 -X -D_GNU_SOURCE -X -lesmtp -X -lssl -X -lcrypto -X -ldl -X -pthread $^ + +clean: + rm -f mailer + +.PHONY: all clean diff --git a/src/mail/mail.vala b/src/mail/mail.vala new file mode 100644 index 0000000..41153f8 --- /dev/null +++ b/src/mail/mail.vala @@ -0,0 +1,209 @@ +/* Copyright 2013, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.Mail")] +public class MailImplementation { + private GMime.Message m; + private GMime.Part? main_text = null; + private GMime.Part? main_html = null; + private GMime.Part[] attachments; + + private GMime.FilterCRLF filter; + + private string[] recipients; + private string? reversepath; + + public MailContact from { set { + string sender = value.name + " " + "<" + value.email + ">"; + reversepath = value.email; + m.set_sender(sender); + }} + + public string subject { + owned get { + var result = m.get_subject(); + return (result == null) ? "" : result; + } + set { + m.set_subject(value); + } + } + + public string message_id { + owned get { + var result = m.get_message_id(); + return (result == null) ? "" : result; + } + set { + m.set_message_id(value); + } + } + + public string reply_to { + owned get { + var result = m.get_reply_to(); + return (result == null) ? "" : result; + } + set { + m.set_reply_to(value); + } + } + + public MailDate date { + owned get { + MailDate result = {}; + m.get_date(out result.date, out result.tz_offset); + return result; + } + set { + m.set_date((ulong) value.date, value.tz_offset); + } + } + + public MailImplementation() { + m = new GMime.Message(true); + m.set_header("X-Mailer", "KtT Shopsystem"); + attachments = new GMime.Part[0]; + filter = new GMime.FilterCRLF(true, true); + recipients = new string[0]; + } + +#if 0 + public void set_from(MailContact contact) { + string sender = contact.name + " " + "<" + contact.email + ">"; + m.set_sender(sender); + } + + public void set_subject(string subject) { + m.set_subject(subject); + } + + public void set_date(uint64 date, int tz_offset) { + m.set_date((ulong) date, tz_offset); + } +#endif + + public void add_recipient(MailContact contact, GMime.RecipientType type) { + m.add_recipient(type, contact.name, contact.email); + recipients += contact.email; + } + + public void set_main_part(string text, MessageType type) { + GMime.DataWrapper content = new GMime.DataWrapper.with_stream( + new GMime.StreamMem.with_buffer(text.data), + GMime.ContentEncoding.DEFAULT); + + GMime.Part? part = new GMime.Part(); + part.set_content_object(content); + + switch(type) { + case MessageType.HTML: + part.set_content_type(new GMime.ContentType.from_string("text/html; charset=utf-8")); + part.set_content_encoding(part.get_best_content_encoding(GMime.EncodingConstraint.7BIT)); + main_html = part; + break; + case MessageType.PLAIN: + default: + part.set_content_type(new GMime.ContentType.from_string("text/plain; charset=utf-8; format=flowed")); + part.set_content_encoding(part.get_best_content_encoding(GMime.EncodingConstraint.7BIT)); + main_text = part; + break; + } + } + + public void add_attachment(string filename, string content_type, uint8[] data) { + GMime.Part part = new GMime.Part(); + + GMime.DataWrapper content = new GMime.DataWrapper.with_stream( + new GMime.StreamMem.with_buffer(data), + GMime.ContentEncoding.BINARY); + + /* configure part */ + part.set_disposition("attachment"); + part.set_filename(filename); + part.set_content_type(new GMime.ContentType.from_string(content_type)); + part.set_content_object(content); + part.set_content_encoding(part.get_best_content_encoding(GMime.EncodingConstraint.7BIT)); + + attachments += part; + } + + private GMime.Object? generate_main() { + if(main_text != null && main_html != null) { + var result = new GMime.Multipart.with_subtype("alternative"); + result.add(main_text); + result.add(main_html); + return result; + } else if(main_text != null) { + return main_text; + } else if(main_html != null) { + return main_html; + } + + return null; + } + + private GMime.Object? generate_attachments() { + if(attachments.length == 1) { + return attachments[0]; + } else if(attachments.length > 1) { + var multipart = new GMime.Multipart.with_subtype("mixed"); + foreach(var attachment in attachments) + multipart.add(attachment); + return multipart; + } + + return null; + } + + private void update_mime_part() { + GMime.Object? main = generate_main(); + GMime.Object? attachments = generate_attachments(); + GMime.Object? mime_message = null; + + if(main != null && attachments != null) { + var multipart = new GMime.Multipart.with_subtype("mixed"); + multipart.add(main); + multipart.add(attachments); + mime_message = multipart; + } else if(main != null) { + mime_message = main; + } else if(attachments != null) { + mime_message = attachments; + } + + m.set_mime_part(mime_message); + } + + [DBus (visible = false)] + public string generate() { + update_mime_part(); + string result = m.to_string(); + uint8[] crlfdata; + size_t prespace; + filter.filter(result.data, 0, out crlfdata, out prespace); + return (string) crlfdata; + } + + [DBus (visible = false)] + public unowned string[] get_recipients() { + return recipients; + } + + [DBus (visible = false)] + public string get_reverse_path() { + return reversepath; + } +} diff --git a/src/mail/mailer-interface.vala b/src/mail/mailer-interface.vala new file mode 100644 index 0000000..43ce244 --- /dev/null +++ b/src/mail/mailer-interface.vala @@ -0,0 +1,60 @@ +/* Copyright 2013, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.Mailer")] +public interface Mailer : Object { + public abstract string create_mail() throws IOError; + public abstract void delete_mail(string path) throws IOError; + public abstract void send_mail(string path) throws IOError; +} + +[DBus (name = "io.mainframe.shopsystem.Mail")] +public interface Mail : Object { + public abstract MailContact from { set; } + public abstract string subject { owned get; set; } + public abstract string message_id { owned get; set; } + public abstract string reply_to { owned get; set; } + public abstract MailDate date { owned get; set; } + + public abstract void add_recipient(MailContact contact, GMime.RecipientType type = GMime.RecipientType.TO) throws IOError; + public abstract void set_main_part(string text, MessageType type = MessageType.PLAIN) throws IOError; + public abstract void add_attachment(string filename, string content_type, uint8[] data) throws IOError; +} + +public struct MailAttachment { + public string filename; + public string filetype; + public uint8[] data; +} + +public struct MailRecipient { + public string name; + public string email; +} + +public struct MailContact { + string name; + string email; +} + +public struct MailDate { + uint64 date; + int tz_offset; +} + +public enum MessageType { + PLAIN, + HTML +} diff --git a/src/mail/mailer.vala b/src/mail/mailer.vala new file mode 100644 index 0000000..58b1b45 --- /dev/null +++ b/src/mail/mailer.vala @@ -0,0 +1,160 @@ +/* Copyright 2013, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.Mailer")] +public class MailerImplementation { + Smtp.Session session; + bool messagecb_done = false; + uint mailcounter = 0; + + struct MailEntry { + uint registration_id; + MailImplementation mail; + } + + HashTable<string,MailEntry?> mails; + MailImplementation current_mail; + + string server; + string username; + string password; + + unowned string? callback(out string? buf, int *len) { + buf = null; + + if(len != null && !messagecb_done) { + buf = current_mail.generate(); + *len = buf.length; + messagecb_done = true; + } + + return buf; + } + + int auth_interaction(Smtp.AuthClientRequest[] requests, char** result) { + for(int i=0; i < requests.length; i++) { + if(Smtp.AuthType.USER in requests[i].flags) { + *(result+i) = username; + } else if(Smtp.AuthType.PASS in requests[i].flags) { + *(result+i) = password; + } + } + return 1; + } + + public MailerImplementation() throws IOError { + int result; + + GMime.init(0); + + Smtp.auth_client_init(); + session = Smtp.Session(); + mails = new HashTable<string,MailEntry?>(str_hash, str_equal); + + /* ignore SIGPIPE, as suggested by libESMTP */ + Posix.signal(Posix.SIGPIPE, Posix.SIG_IGN); + + /* get configuration */ + Config config = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.Config", "/io/mainframe/shopsystem/config"); + try { + var cfgserv = config.get_string("MAIL", "server"); + var cfgport = config.get_integer("MAIL", "port"); + server = @"$cfgserv:$cfgport"; + } catch(KeyFileError e) { + throw new IOError.FAILED("server or port configuration is missing"); + } + + try { + username = config.get_string("MAIL", "username"); + password = config.get_string("MAIL", "password"); + } catch(KeyFileError e) { + username = ""; + password = ""; + } + + /* setup server */ + result = session.set_server(server); + if(result == 0) + throw new IOError.FAILED("could not setup server"); + + /* Use TLS if possible */ + result = session.starttls_enable(Smtp.StartTlsOption.ENABLED); + if(result == 0) + throw new IOError.FAILED("could not setup TLS"); + + /* setup authentication */ + if(username != "") { + var auth = Smtp.auth_create_context(); + auth.set_mechanism_flags(Smtp.AUTH_PLUGIN_PLAIN, 0); + auth.set_interact_cb(auth_interaction); + session.auth_set_context(auth); + } + } + + ~MailerImplementation() { + Smtp.auth_client_exit(); + GMime.shutdown(); + } + + public string create_mail() throws IOError { + string path = @"/io/mainframe/shopsystem/mail/$mailcounter"; + + var mail = new MailImplementation(); + + MailEntry entry = { + mail_bus.register_object(path, mail), + mail + }; + + mails[path] = entry; + mailcounter++; + + return path; + } + + public void delete_mail(string path) throws IOError { + if(!(path in mails)) + throw new IOError.NOT_FOUND("No such mail"); + + mail_bus.unregister_object(mails[path].registration_id); + mails.remove(path); + } + + public void send_mail(string path) throws IOError { + if(!(path in mails)) + throw new IOError.NOT_FOUND("No such mail"); + + var message = session.add_message(); + + messagecb_done = false; + current_mail = mails[path].mail; + message.set_messagecb(callback); + + foreach(var recipient in current_mail.get_recipients()) { + message.add_recipient(recipient); + } + message.set_reverse_path(current_mail.get_reverse_path()); + + int result = session.start_session(); + if(result == 0) + throw new IOError.FAILED("eSMTP: Start Session failed!"); + + unowned Smtp.Status status = message.transfer_status(); + if(status.code < 200 || status.code >= 300) + throw new IOError.FAILED("Reply from SMTP-Server: %s", status.text); + + delete_mail(path); + } +} diff --git a/src/mail/main.vala b/src/mail/main.vala new file mode 100644 index 0000000..1aeb3eb --- /dev/null +++ b/src/mail/main.vala @@ -0,0 +1,46 @@ +/* Copyright 2013, Sebastian Reichel <sre@ring0.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +MailerImplementation m; +DBusConnection mail_bus; + +public static int main(string[] args) { + try { + m = new MailerImplementation(); + } catch(IOError e) { + stderr.printf("Error: %s\n", e.message); + } + + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.Mail", + BusNameOwnerFlags.NONE, + on_mail_bus_aquired, + () => {}, + () => stderr.printf("Error: Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_mail_bus_aquired(DBusConnection con) { + try { + mail_bus = con; + con.register_object("/io/mainframe/shopsystem/mailer", m); + } catch(IOError e) { + stderr.printf("Error: Could not register service\n"); + } +} |