From 549c4f54cdbf883da8a4185d08b2232edd63af63 Mon Sep 17 00:00:00 2001 From: Nedim <24845593+nedimf@users.noreply.github.com> Date: Sat, 11 Jul 2020 21:05:24 +0200 Subject: [PATCH] net: implementation of SMTP RFC 2821 (#5807) --- vlib/net/smtp/smtp.v | 216 ++++++++++++++++++++++++++++++++++++++ vlib/net/smtp/smtp_test.v | 18 ++++ 2 files changed, 234 insertions(+) create mode 100644 vlib/net/smtp/smtp.v create mode 100644 vlib/net/smtp/smtp_test.v diff --git a/vlib/net/smtp/smtp.v b/vlib/net/smtp/smtp.v new file mode 100644 index 0000000000..19524faab3 --- /dev/null +++ b/vlib/net/smtp/smtp.v @@ -0,0 +1,216 @@ +module smtp + +/* +* +* smtp module +* Created by: nedimf (07/2020) +*/ +import net +import encoding.base64 +import strings + +struct Commands { + ehlo string = 'EHLO Alice\r\n' + auth_plain string = 'AUTH PLAIN ' + mail_from string = 'MAIL FROM:' + recpt_to string = 'RCPT TO:' + data string = 'DATA\r\n' + subject string = 'Subject:' + end_msg string = '\r\n.\r\n' + quit string = 'QUIT\r\n' +} + + +// Sends an email trough SMTP socket +pub fn send_mail(mailserver string, port int, username, password, subject, from, to, body, type_body string, debug bool)? { + client := connect(mailserver, port, debug)? + send_ehlo(client, debug) + auth(client, username, password, debug) + send_mailfrom(client, from, debug) + send_mailto(client, to, debug) + send_data(client, debug) + if type_body == 'html' { + send_html_body(client, subject, from, to, body, debug) + } else { + send_text_body(client, subject, from, to, body, debug) + } + send_quit(client, debug) +} + +// Creates socket connection with TCP server on provided port and returns net.Socket +pub fn connect(mailserver string, port int, debug bool) ?(net.Socket) { + mut client := net.dial(mailserver, port)? + bytes, blen := client.recv(1024) + recv := recieved(bytes, blen) + if recv.len >= 3 { + is_debug(debug, recv) + status := recv[..3] + if status.int() != 220 { + return error('Replay (220) from server has not been recieved.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return client +} + +// Sends EHLO command to connected server. EHLO (Extended HELO) tells mailserver that our connection uses ESMTP +pub fn send_ehlo(socket net.Socket, debug bool) ?bool { + cmd := Commands{} + recv := send(socket, cmd.ehlo) + if recv.len >= 3 { + status := recv[..3] + is_debug(debug, recv) + if status.int() != 250 { + return error('Replay (250) from server has not been recieved for EHLO.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return true +} + +// Closing the connection with server +pub fn send_quit(socket net.Socket, debug bool) ?bool { + cmd := Commands{} + recv := send(socket, cmd.quit) + if recv.len >= 3 { + status := recv[..3] + is_debug(debug, recv) + if status.int() != 221 { + return error('Replay (221) from server has not been recieved for QUIT.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return true +} + +fn send_mailfrom(socket net.Socket, from string, debug bool) ?bool { + cmd := Commands{} + recv := send(socket, cmd.mail_from + ' <$from>\r\n') + if recv.len >= 3 { + status := recv[..3] + is_debug(debug, recv) + if status.int() != 221 { + return error('Replay (221) from server has not been recieved for MAIL FROM.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return true +} + +fn send_mailto(socket net.Socket, to string, debug bool) ?bool { + cmd := Commands{} + recv := send(socket, cmd.recpt_to + ' <$to>\r\n') + if recv.len >= 3 { + status := recv[..3] + is_debug(debug, recv) + if status.int() != 221 { + return error('Replay (221) from server has not been recieved for MAIL TO.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return true +} + +fn send_data(socket net.Socket, debug bool) ?bool { + cmd := Commands{} + recv := send(socket, cmd.data) + if recv.len >= 3 { + status := recv[..3] + is_debug(debug, recv) + if status.int() != 354 { + return error('Replay (354) from server has not been recieved for DATA.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return true +} + +fn send(socket net.Socket, string_to_send string) string { + socket.send_string(string_to_send) + bytes, blen := socket.recv(1024) + return recieved(bytes, blen) +} + +fn send_text_body(socket net.Socket, subject, from, to, body string, debug bool) ? { + socket.send_string('From: $from\r\n') + socket.send_string('To: $to\r\n') + socket.send_string('Subject: $subject\r\n') + socket.send_string('\r\n\r\n') + socket.send_string(body) + socket.send_string('\r\n.\r\n') + bytes, blen := socket.recv(1024) + recv := recieved(bytes, blen) + println(recv) + if recv.len >= 3 { + status := recv[..3] + is_debug(debug, recv) + if status.int() != 250 { + return error('Replay (250) from server has not been recieved for EHLO.\nReplay recieved: $status') + } + println('V: Mail sent!') + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } +} + +fn send_html_body(socket net.Socket, subject, from, to, body string, debug bool) ? { + socket.send_string('From: $from\r\n') + socket.send_string('To: $to\r\n') + socket.send_string('Subject: $subject\r\n') + socket.send_string('Content-Type: text/html; charset=ISO-8859-1') + socket.send_string('\r\n\r\n') + socket.send_string(body) + socket.send_string('\r\n.\r\n') + bytes, blen := socket.recv(1024) + recv := recieved(bytes, blen) + if recv.len >= 3 { + is_debug(debug, recv) + status := recv[..3] + if status.int() != 250 { + return error('Replay (250) from server has not been recieved for EHLO.\nReplay recieved: $status') + } + println('V: Mail sent!') + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } +} + +fn auth(client net.Socket, username, password string, debug bool) ?bool { + cmd := Commands{} + mut sb := strings.new_builder(100) + sb.write_b(0) + sb.write(username) + sb.write_b(0) + sb.write(password) + mut x := sb.str() + x = base64.encode(x) + auth_cmd := cmd.auth_plain + x + '\r\n' + recv := send(client, auth_cmd) + if recv.len >= 3 { + is_debug(debug, recv) + status := recv[..3] + if status.int() != 235 { + return error('Replay (235) from server has not been recieved for AUTH.\nReplay recieved: $status') + } + } else { + return error('Recieved data from SMTP server is not returning supported values\nReturned values: $recv') + } + return true +} + +fn recieved(bytes byteptr, blen int) string { + return tos(bytes, blen) +} + +fn is_debug(debug bool, pr string) { + if debug == true { + println('\n...\n') + println(pr) + } +} diff --git a/vlib/net/smtp/smtp_test.v b/vlib/net/smtp/smtp_test.v new file mode 100644 index 0000000000..87824d5b63 --- /dev/null +++ b/vlib/net/smtp/smtp_test.v @@ -0,0 +1,18 @@ +import smtp + +fn test_smtp() { + + server := 'smtp.mailtrap.io' + port := 2525 + username := '46d1daf3ff04f' + password := '1e8ba2dbf19f4f' + subject := 'Hello from V' + from := 'dev@vlang.io' + to := 'dev@vlang.io' + msg := '

Hi,from V module, this message was sent by SMTP!

' + body_type := 'html' + debug := true //use while debugging + + smtp.send_mail(server, port, username, password, subject, from, to, msg, body_type, + debug) +}