oops! fixed invalid binding name (rare case, never happened before ;-)
[chiroptera.git] / chibackend / net / pop3.d
blob8cb08b53b1649e73fc89e11832baf6eecc4a2fb6
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module chibackend.net.pop3 /*is aliced*/;
19 import std.socket;
21 import iv.alice;
22 import iv.base64;
23 import iv.cmdcon;
24 import iv.dynstring;
25 import iv.sslsocket;
27 import chibackend : DynStr;
28 import chibackend.net.linesocket;
31 usage:
33 receiving
34 =========
36 conwriteln("*** [", name, "]: connecting...");
37 auto pop3 = new SocketPOP3(server);
38 scope(exit) pop3.close();
39 conwriteln("[", name, "]: authenticating...");
40 pop3.auth(user, pass);
41 auto newmsg = pop3.getNewMailCount;
42 if (newmsg == 0) {
43 conwriteln("[", name, "]: no new messages");
44 return;
46 conwriteln("[", name, "]: ", newmsg, " new message", (newmsg > 1 ? "s" : ""));
47 foreach (immutable int popidx; 1..newmsg+1) {
48 auto msg = pop3.getMessage(popidx); // full message, with the ending dot
49 //auto msg = pop3.getMessage!true(popidx); // full message, with the ending dot, and exact terminators
50 // process
51 pop3.deleteMessage(popidx);
55 sending
56 ========
57 string from = art.frommail;
58 if (from.length == 0) { conwriteln("SMTP ERROR: no FROM!"); return false; }
60 auto to = extractToMail(art);
61 if (to.length == 0) { conwriteln("SMTP ERROR: no TO!"); return false; }
63 SocketSMTP nsk;
64 try {
65 nsk = new SocketSMTP(sendserver);
66 } catch (Exception e) {
67 conwriteln("[", name, "]: connection error: ", e.msg);
68 return false;
70 scope(exit) nsk.close();
72 try {
73 if (!smtpNoAuth) nsk.auth(email, user, pass);
74 nsk.sendMessage(from, to, art.getTextToSend);
75 } catch (Exception e) {
76 conwriteln("[", name, "]: sending error: ", e.msg);
77 return false;
82 // ////////////////////////////////////////////////////////////////////////// //
83 public final class SocketPOP3 : SocketLine {
84 public:
85 this (string server, bool debugdump=false) {
86 super(server, plainport:110, tlsport:995, debugdump:debugdump);
87 scope(failure) abort(doshutdown:false);
88 auto hello = readLine();
89 if (!isOK(hello)) throw new Exception("invalid hello (no ok)"); // ("~hello.idup~")");
92 override void quit () {
93 if (!active) return;
94 doSend("QUIT");
95 auto ln = readLine();
98 void auth (const(char)[] uname, const(char)[] upass) {
99 doSendCmdArg("USER", uname);
100 auto ln = readLine();
101 if (!isOK(ln)) throw new Exception("cannot auth");
102 doSendCmdArg("PASS", upass);
103 ln = readLine();
104 if (!isOK(ln)) throw new Exception("cannot auth");
107 int getNewMailCount () {
108 doSend("STAT");
109 auto ln = readLine();
110 if (!isOK(ln)) throw new Exception("cannot stat");
111 getStrToken(ln);
112 return getToken!int(ln);
115 // returns full unmodified message, with the ending dot line
116 // if `exact` is true, returns line with exact terminators
117 // can return truncated message if it is too big
118 DynStr getMessage(bool exact=false) (int idx, bool useRETR=true) {
119 if (idx < 1) throw new Exception("invalid message index");
120 // "RETR" seems to delete messages on some servers so there is a way to use "TOP".
121 // yet the message may contain more lines than we specified in "TOP", therefore
122 // default choice is "RETR". it didn't caused problems so far, but who knows...
123 if (useRETR) {
124 doSendCmdArg("RETR", idx);
125 } else {
126 import core.stdc.stdio : snprintf;
127 char[128] buf = void;
128 auto len = snprintf(buf.ptr, buf.length, "TOP %d 65534", idx);
129 doSendCmdArg(buf[0..len]);
131 auto ln = readLine();
132 if (!isOK(ln)) throw new Exception("cannot retr");
133 DynStr res;
134 bool toobig = false;
135 for (;;) {
136 ln = readLine!exact();
137 if (!toobig) {
138 res ~= ln;
139 static if (!exact) res ~= '\n';
141 if (ln.length == 0 || ln[0] != '.') continue;
142 if (ln.length == 1) break;
143 static if (exact) {
144 if (ln.length == 3 && ln == ".\r\n") break;
145 if (ln.length == 2 && ln == ".\n") break;
147 if (!toobig && res.length > 1024*1024*128) {
148 conwriteln("WARNING: mail message too big!");
149 toobig = true;
152 return res;
155 void deleteMessage (int idx) {
156 if (idx < 1) throw new Exception("invalid message index");
157 doSendCmdArg("DELE", idx);
158 auto ln = readLine();
159 if (!isOK(ln)) throw new Exception("cannot retr");
162 private:
163 static bool isOK (const(char)[] s) {
164 if (s.length < 3) return false;
165 if (s[0] == '+' && (s[1] == 'O' || s[1] == 'o') && (s[2] == 'K' || s[2] == 'k')) return (s.length == 3 || s[3] <= ' ');
166 if (s.length > 8+3) {
167 import iv.strex : indexOf;
168 auto idx = s.indexOf("+OK Gpop ready for requests from ");
169 if (idx >= 0) return true;
171 return false;
176 // ////////////////////////////////////////////////////////////////////////// //
177 public final class SocketSMTP : SocketLine {
178 private:
179 dynstring srv; // for hello
181 private:
182 const(char)[] getResponse () {
183 for (;;) {
184 auto ln = readLine();
185 if (ln.length < 4 || ln[3] <= ' ') return ln;
189 public:
190 this (string server, bool adodump=true) {
191 srv = server;
192 if (srv.length >= 4 && (srv[0..4] == "ssl:" || srv[0..4] == "tls:")) srv = server[4..$];
193 super(server, 25, 465, adodump);
194 scope(failure) abort(doshutdown:false);
195 auto hello = getResponse();
196 if (hello.length < 3 || hello[0..2] != "22") throw new Exception("invalid hello (not 22)");
197 doSendCmdArg("HELO", srv);
198 hello = getResponse();
199 if (hello.length < 3 || hello[0..2] != "25") throw new Exception("invalid hello (not 25)");
202 override void quit () {
203 if (!active) return;
204 doSend("QUIT");
205 auto ln = getResponse();
208 void auth (const(char)[] authority, const(char)[] user, const(char)[] pass) {
209 auto authstr = base64Encode!char(authority~'\0'~user~'\0'~pass);
210 doSendCmdArg("AUTH PLAIN", authstr);
211 auto ln = getResponse();
212 if (ln.length < 3 || ln[0..2] != "23") throw new Exception("authentication failed");
215 // `dsdata` should be dot-stuffed, and end with proper termination dot line (with CRLF)
216 void sendMessage (const(char)[] from, const(char)[] to, const(char)[] dsdata) {
217 dynstring mf = "MAIL FROM: <";
218 mf ~= from;
219 mf ~= ">";
220 doSend(mf.getData);
221 auto ln = getResponse();
222 if (ln.length < 3 || ln[0..2] != "25") throw new Exception("sending from failed");
223 mf = "RCPT TO: <";
224 mf ~= to;
225 mf ~= ">";
226 doSend(mf.getData);
227 ln = getResponse();
228 if (ln.length < 3 || ln[0..2] != "25") throw new Exception("sending to failed");
229 doSend("DATA");
230 ln = getResponse();
231 if (ln.length < 3 || ln[0..2] != "35") throw new Exception("sending data failed");
232 doSendRaw(dsdata);
233 ln = getResponse();
234 if (ln.length < 3 || ln[0..2] != "25") throw new Exception("sending data failed");