summaryrefslogtreecommitdiffstats
path: root/src/mail
diff options
context:
space:
mode:
Diffstat (limited to 'src/mail')
-rw-r--r--src/mail/Makefile9
-rw-r--r--src/mail/mail.vala209
-rw-r--r--src/mail/mailer-interface.vala60
-rw-r--r--src/mail/mailer.vala160
-rw-r--r--src/mail/main.vala46
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");
+ }
+}