smtp.hpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #pragma once
  2. #include <nall/base64.hpp>
  3. #include <nall/stdint.hpp>
  4. #include <nall/string.hpp>
  5. #if !defined(PLATFORM_WINDOWS)
  6. #include <sys/types.h>
  7. #include <sys/socket.h>
  8. #include <netinet/in.h>
  9. #include <netdb.h>
  10. #else
  11. #include <winsock2.h>
  12. #include <ws2tcpip.h>
  13. #endif
  14. namespace nall {
  15. struct SMTP {
  16. enum class Format : uint { Plain, HTML };
  17. auto server(string server, uint16_t port = 25) -> void;
  18. auto from(string mail, string name = "") -> void;
  19. auto to(string mail, string name = "") -> void;
  20. auto cc(string mail, string name = "") -> void;
  21. auto bcc(string mail, string name = "") -> void;
  22. auto attachment(const uint8_t* data, uint size, string name) -> void;
  23. auto attachment(string filename, string name = "") -> bool;
  24. auto subject(string subject) -> void;
  25. auto body(string body, Format format = Format::Plain) -> void;
  26. auto send() -> bool;
  27. auto message() -> string;
  28. auto response() -> string;
  29. #if defined(API_WINDOWS)
  30. auto close(int) -> int;
  31. SMTP();
  32. #endif
  33. private:
  34. struct Information {
  35. string server;
  36. uint16_t port;
  37. struct Contact {
  38. string mail;
  39. string name;
  40. };
  41. Contact from;
  42. vector<Contact> to;
  43. vector<Contact> cc;
  44. vector<Contact> bcc;
  45. struct Attachment {
  46. vector<uint8_t> buffer;
  47. string name;
  48. };
  49. string subject;
  50. string body;
  51. Format format = Format::Plain;
  52. vector<Attachment> attachments;
  53. string message;
  54. string response;
  55. } info;
  56. auto send(int sock, const string& text) -> bool;
  57. auto recv(int sock) -> string;
  58. auto boundary() -> string;
  59. auto filename(const string& filename) -> string;
  60. auto contact(const Information::Contact& contact) -> string;
  61. auto contacts(const vector<Information::Contact>& contacts) -> string;
  62. auto split(const string& text) -> string;
  63. };
  64. inline auto SMTP::server(string server, uint16_t port) -> void {
  65. info.server = server;
  66. info.port = port;
  67. }
  68. inline auto SMTP::from(string mail, string name) -> void {
  69. info.from = {mail, name};
  70. }
  71. inline auto SMTP::to(string mail, string name) -> void {
  72. info.to.append({mail, name});
  73. }
  74. inline auto SMTP::cc(string mail, string name) -> void {
  75. info.cc.append({mail, name});
  76. }
  77. inline auto SMTP::bcc(string mail, string name) -> void {
  78. info.bcc.append({mail, name});
  79. }
  80. inline auto SMTP::attachment(const uint8_t* data, uint size, string name) -> void {
  81. vector<uint8_t> buffer;
  82. buffer.resize(size);
  83. memcpy(buffer.data(), data, size);
  84. info.attachments.append({std::move(buffer), name});
  85. }
  86. inline auto SMTP::attachment(string filename, string name) -> bool {
  87. if(!file::exists(filename)) return false;
  88. if(name == "") name = notdir(filename);
  89. auto buffer = file::read(filename);
  90. info.attachments.append({std::move(buffer), name});
  91. return true;
  92. }
  93. inline auto SMTP::subject(string subject) -> void {
  94. info.subject = subject;
  95. }
  96. inline auto SMTP::body(string body, Format format) -> void {
  97. info.body = body;
  98. info.format = format;
  99. }
  100. inline auto SMTP::send() -> bool {
  101. info.message.append("From: =?UTF-8?B?", Base64::encode(contact(info.from)), "?=\r\n");
  102. info.message.append("To: =?UTF-8?B?", Base64::encode(contacts(info.to)), "?=\r\n");
  103. info.message.append("Cc: =?UTF-8?B?", Base64::encode(contacts(info.cc)), "?=\r\n");
  104. info.message.append("Subject: =?UTF-8?B?", Base64::encode(info.subject), "?=\r\n");
  105. string uniqueID = boundary();
  106. info.message.append("MIME-Version: 1.0\r\n");
  107. info.message.append("Content-Type: multipart/mixed; boundary=", uniqueID, "\r\n");
  108. info.message.append("\r\n");
  109. string format = (info.format == Format::Plain ? "text/plain" : "text/html");
  110. info.message.append("--", uniqueID, "\r\n");
  111. info.message.append("Content-Type: ", format, "; charset=UTF-8\r\n");
  112. info.message.append("Content-Transfer-Encoding: base64\r\n");
  113. info.message.append("\r\n");
  114. info.message.append(split(Base64::encode(info.body)), "\r\n");
  115. info.message.append("\r\n");
  116. for(auto& attachment : info.attachments) {
  117. info.message.append("--", uniqueID, "\r\n");
  118. info.message.append("Content-Type: application/octet-stream\r\n");
  119. info.message.append("Content-Transfer-Encoding: base64\r\n");
  120. info.message.append("Content-Disposition: attachment; size=", attachment.buffer.size(), "; filename*=UTF-8''", filename(attachment.name), "\r\n");
  121. info.message.append("\r\n");
  122. info.message.append(split(Base64::encode(attachment.buffer)), "\r\n");
  123. info.message.append("\r\n");
  124. }
  125. info.message.append("--", uniqueID, "--\r\n");
  126. addrinfo hints;
  127. memset(&hints, 0, sizeof(addrinfo));
  128. hints.ai_family = AF_UNSPEC;
  129. hints.ai_socktype = SOCK_STREAM;
  130. hints.ai_flags = AI_PASSIVE;
  131. addrinfo* serverinfo;
  132. int status = getaddrinfo(info.server, string(info.port), &hints, &serverinfo);
  133. if(status != 0) return false;
  134. int sock = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol);
  135. if(sock == -1) return false;
  136. int result = connect(sock, serverinfo->ai_addr, serverinfo->ai_addrlen);
  137. if(result == -1) return false;
  138. string response;
  139. info.response.append(response = recv(sock));
  140. if(!response.beginswith("220 ")) { close(sock); return false; }
  141. send(sock, {"HELO ", info.server, "\r\n"});
  142. info.response.append(response = recv(sock));
  143. if(!response.beginswith("250 ")) { close(sock); return false; }
  144. send(sock, {"MAIL FROM: <", info.from.mail, ">\r\n"});
  145. info.response.append(response = recv(sock));
  146. if(!response.beginswith("250 ")) { close(sock); return false; }
  147. for(auto& contact : info.to) {
  148. send(sock, {"RCPT TO: <", contact.mail, ">\r\n"});
  149. info.response.append(response = recv(sock));
  150. if(!response.beginswith("250 ")) { close(sock); return false; }
  151. }
  152. for(auto& contact : info.cc) {
  153. send(sock, {"RCPT TO: <", contact.mail, ">\r\n"});
  154. info.response.append(response = recv(sock));
  155. if(!response.beginswith("250 ")) { close(sock); return false; }
  156. }
  157. for(auto& contact : info.bcc) {
  158. send(sock, {"RCPT TO: <", contact.mail, ">\r\n"});
  159. info.response.append(response = recv(sock));
  160. if(!response.beginswith("250 ")) { close(sock); return false; }
  161. }
  162. send(sock, {"DATA\r\n"});
  163. info.response.append(response = recv(sock));
  164. if(!response.beginswith("354 ")) { close(sock); return false; }
  165. send(sock, {info.message, "\r\n", ".\r\n"});
  166. info.response.append(response = recv(sock));
  167. if(!response.beginswith("250 ")) { close(sock); return false; }
  168. send(sock, {"QUIT\r\n"});
  169. info.response.append(response = recv(sock));
  170. //if(!response.beginswith("221 ")) { close(sock); return false; }
  171. close(sock);
  172. return true;
  173. }
  174. inline auto SMTP::message() -> string {
  175. return info.message;
  176. }
  177. inline auto SMTP::response() -> string {
  178. return info.response;
  179. }
  180. inline auto SMTP::send(int sock, const string& text) -> bool {
  181. const char* data = text.data();
  182. uint size = text.size();
  183. while(size) {
  184. int length = ::send(sock, (const char*)data, size, 0);
  185. if(length == -1) return false;
  186. data += length;
  187. size -= length;
  188. }
  189. return true;
  190. }
  191. inline auto SMTP::recv(int sock) -> string {
  192. vector<uint8_t> buffer;
  193. while(true) {
  194. char c;
  195. if(::recv(sock, &c, sizeof(char), 0) < 1) break;
  196. buffer.append(c);
  197. if(c == '\n') break;
  198. }
  199. buffer.append(0);
  200. return buffer;
  201. }
  202. inline auto SMTP::boundary() -> string {
  203. random_lfsr random;
  204. random.seed(time(0));
  205. string boundary;
  206. for(uint n = 0; n < 16; n++) boundary.append(hex<2>(random()));
  207. return boundary;
  208. }
  209. inline auto SMTP::filename(const string& filename) -> string {
  210. string result;
  211. for(auto& n : filename) {
  212. if(n <= 32 || n >= 127) result.append("%", hex<2>(n));
  213. else result.append(n);
  214. }
  215. return result;
  216. }
  217. inline auto SMTP::contact(const Information::Contact& contact) -> string {
  218. if(!contact.name) return contact.mail;
  219. return {"\"", contact.name, "\" <", contact.mail, ">"};
  220. }
  221. inline auto SMTP::contacts(const vector<Information::Contact>& contacts) -> string {
  222. string result;
  223. for(auto& contact : contacts) {
  224. result.append(this->contact(contact), "; ");
  225. }
  226. result.trimRight("; ", 1L);
  227. return result;
  228. }
  229. inline auto SMTP::split(const string& text) -> string {
  230. string result;
  231. uint offset = 0;
  232. while(offset < text.size()) {
  233. uint length = min(76, text.size() - offset);
  234. if(length < 76) {
  235. result.append(text.slice(offset));
  236. } else {
  237. result.append(text.slice(offset, 76), "\r\n");
  238. }
  239. offset += length;
  240. }
  241. return result;
  242. }
  243. #if defined(API_WINDOWS)
  244. inline auto SMTP::close(int sock) -> int {
  245. return closesocket(sock);
  246. }
  247. inline SMTP::SMTP() {
  248. int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  249. if(sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) {
  250. WSADATA wsaData;
  251. if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
  252. WSACleanup();
  253. return;
  254. }
  255. } else {
  256. close(sock);
  257. }
  258. }
  259. #endif
  260. }