View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2009  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail;
20  
21  import davmail.exception.DavMailException;
22  import davmail.exchange.ExchangeSession;
23  import davmail.ui.tray.DavGatewayTray;
24  import org.apache.log4j.Logger;
25  
26  import java.io.*;
27  import java.net.Socket;
28  import java.nio.charset.StandardCharsets;
29  
30  
31  /**
32   * Generic connection common to pop3 and smtp implementations
33   */
34  public abstract class AbstractConnection extends Thread implements Closeable {
35  
36      protected enum State {
37          INITIAL, LOGIN, USER, PASSWORD, AUTHENTICATED, STARTMAIL, RECIPIENT, MAILDATA
38      }
39  
40      protected static class LineReaderInputStream extends PushbackInputStream {
41          final String encoding;
42  
43          protected LineReaderInputStream(InputStream in, String encoding) {
44              super(in);
45              if (encoding == null) {
46                  this.encoding = "ASCII";
47              } else {
48                  this.encoding = encoding;
49              }
50          }
51  
52          public String readLine() throws IOException {
53              ByteArrayOutputStream baos = null;
54              int b;
55              while ((b = read()) > -1) {
56                  if (b == '\r') {
57                      int next = read();
58                      if (next != '\n') {
59                          unread(next);
60                      }
61                      break;
62                  } else if (b == '\n') {
63                      break;
64                  }
65                  if (baos == null) {
66                      baos = new ByteArrayOutputStream();
67                  }
68                  baos.write(b);
69              }
70              if (baos != null) {
71                  return baos.toString(encoding);
72              } else {
73                  return null;
74              }
75          }
76  
77          /**
78           * Read byteSize bytes from inputStream, return content as String.
79           *
80           * @param byteSize content size
81           * @return content
82           * @throws IOException on error
83           */
84          public String readContentAsString(int byteSize) throws IOException {
85              return new String(readContent(byteSize), encoding);
86          }
87  
88          /**
89           * Read byteSize bytes from inputStream, return content as byte array.
90           *
91           * @param byteSize content size
92           * @return content
93           * @throws IOException on error
94           */
95          public byte[] readContent(int byteSize) throws IOException {
96              byte[] buffer = new byte[byteSize];
97              int startIndex = 0;
98              int count = 0;
99              while (count >= 0 && startIndex < byteSize) {
100                 count = read(buffer, startIndex, byteSize - startIndex);
101                 startIndex += count;
102             }
103             if (startIndex < byteSize) {
104                 throw new DavMailException("EXCEPTION_END_OF_STREAM");
105             }
106 
107             return buffer;
108         }
109     }
110 
111     protected final Socket client;
112 
113     protected LineReaderInputStream in;
114     protected OutputStream os;
115     // user name and password initialized through connection
116     protected String userName;
117     protected String password;
118     // connection state
119     protected State state = State.INITIAL;
120     // Exchange session proxy
121     protected ExchangeSession session;
122 
123     /**
124      * Only set the thread name and socket
125      *
126      * @param name         thread type name
127      * @param clientSocket client socket
128      */
129     public AbstractConnection(String name, Socket clientSocket) {
130         super(name + '-' + clientSocket.getPort());
131         this.client = clientSocket;
132         setDaemon(true);
133     }
134 
135     /**
136      * Initialize the streams and set thread name.
137      *
138      * @param name         thread type name
139      * @param clientSocket client socket
140      * @param encoding     socket stream encoding
141      */
142     public AbstractConnection(String name, Socket clientSocket, String encoding) {
143         super(name + '-' + clientSocket.getPort());
144         this.client = clientSocket;
145         logConnection("CONNECT", "");
146         try {
147             in = new LineReaderInputStream(client.getInputStream(), encoding);
148             os = new BufferedOutputStream(client.getOutputStream());
149         } catch (IOException e) {
150             close();
151             DavGatewayTray.error(new BundleMessage("LOG_EXCEPTION_GETTING_SOCKET_STREAMS"), e);
152         }
153     }
154 
155     public void logConnection(String action, String userName) {
156         Logger.getLogger("davmail.connection").info(action+" - "+client.getInetAddress().getHostAddress()+":"+client.getPort()+" " + userName);
157     }
158 
159     /**
160      * Send message to client followed by CRLF.
161      *
162      * @param message message
163      * @throws IOException on error
164      */
165     public void sendClient(String message) throws IOException {
166         sendClient(null, message);
167     }
168 
169     /**
170      * Send prefix and message to client followed by CRLF.
171      *
172      * @param prefix  prefix
173      * @param message message
174      * @throws IOException on error
175      */
176     public void sendClient(String prefix, String message) throws IOException {
177         if (prefix != null) {
178             os.write(prefix.getBytes(StandardCharsets.UTF_8));
179             DavGatewayTray.debug(new BundleMessage("LOG_SEND_CLIENT_PREFIX_MESSAGE", prefix, message));
180         } else {
181             DavGatewayTray.debug(new BundleMessage("LOG_SEND_CLIENT_MESSAGE", message));
182         }
183         os.write(message.getBytes(StandardCharsets.UTF_8));
184         os.write((char) 13);
185         os.write((char) 10);
186         os.flush();
187     }
188 
189     /**
190      * Send only bytes to client.
191      *
192      * @param messageBytes content
193      * @throws IOException on error
194      */
195     public void sendClient(byte[] messageBytes) throws IOException {
196         sendClient(messageBytes, 0, messageBytes.length);
197     }
198 
199     /**
200      * Send only bytes to client.
201      *
202      * @param messageBytes content
203      * @param offset       the start offset in the data.
204      * @param length       the number of bytes to write.
205      * @throws IOException on error
206      */
207     public void sendClient(byte[] messageBytes, int offset, int length) throws IOException {
208         //StringBuffer logBuffer = new StringBuffer("> ");
209         //logBuffer.append(new String(messageBytes, offset, length));
210         //DavGatewayTray.debug(logBuffer.toString());
211         os.write(messageBytes, offset, length);
212         os.flush();
213     }
214 
215     /**
216      * Read a line from the client connection.
217      * Log message to logger
218      *
219      * @return command line or null
220      * @throws IOException when unable to read line
221      */
222     public String readClient() throws IOException {
223         String line = in.readLine();
224         if (line != null) {
225             if (line.startsWith("PASS")) {
226                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_PASS"));
227                 // SMTP LOGIN
228             } else if (line.startsWith("AUTH LOGIN ")) {
229                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTH_LOGIN"));
230                 // IMAP LOGIN
231             } else if (state == State.INITIAL && line.indexOf(' ') >= 0 &&
232                     line.substring(line.indexOf(' ') + 1).toUpperCase().startsWith("LOGIN")) {
233                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_LOGIN"));
234             } else if (state == State.PASSWORD) {
235                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_PASSWORD"));
236                 // HTTP Basic Authentication
237             } else if (line.startsWith("Authorization:")) {
238                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTHORIZATION"));
239             } else if (line.startsWith("AUTH PLAIN")) {
240                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTH_PLAIN"));
241             } else {
242                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_LINE", line));
243             }
244         }
245         DavGatewayTray.switchIcon();
246         return line;
247     }
248 
249     /**
250      * Close client connection, streams and Exchange session .
251      */
252     public void close() {
253         logConnection("DISCONNECT", "");
254         if (in != null) {
255             try {
256                 in.close();
257             } catch (IOException e2) {
258                 // ignore
259             }
260         }
261         if (os != null) {
262             try {
263                 os.close();
264             } catch (IOException e2) {
265                 // ignore
266             }
267         }
268         try {
269             client.close();
270         } catch (IOException e2) {
271             DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_SOCKET"), e2);
272         }
273     }
274 }