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 byteArrayOutputStream = null;
54              int b;
55              while ((b = read()) > -1) {
56                  // Handles line ending variations
57                  if (b == '\r') {
58                      int next = read();
59                      if (next != '\n') {
60                          unread(next);
61                      }
62                      break;
63                  } else if (b == '\n') {
64                      break;
65                  }
66                  if (byteArrayOutputStream == null) {
67                      byteArrayOutputStream = new ByteArrayOutputStream();
68                  }
69                  byteArrayOutputStream.write(b);
70              }
71              if (byteArrayOutputStream != null) {
72                  return byteArrayOutputStream.toString(encoding);
73              } else {
74                  return null;
75              }
76          }
77  
78          /**
79           * Read byteSize bytes from inputStream, return content as String.
80           *
81           * @param byteSize content size
82           * @return content
83           * @throws IOException on error
84           */
85          public String readContentAsString(int byteSize) throws IOException {
86              return new String(readContent(byteSize), encoding);
87          }
88  
89          /**
90           * Read byteSize bytes from inputStream, return content as a byte array.
91           *
92           * @param byteSize content size
93           * @return content
94           * @throws IOException on error
95           */
96          public byte[] readContent(int byteSize) throws IOException {
97              byte[] buffer = new byte[byteSize];
98              int startIndex = 0;
99              int count = 0;
100             while (count >= 0 && startIndex < byteSize) {
101                 count = read(buffer, startIndex, byteSize - startIndex);
102                 startIndex += count;
103             }
104             if (startIndex < byteSize) {
105                 throw new DavMailException("EXCEPTION_END_OF_STREAM");
106             }
107 
108             return buffer;
109         }
110     }
111 
112     protected final Socket client;
113 
114     protected LineReaderInputStream in;
115     protected OutputStream os;
116     // username and password initialized through connection
117     protected String userName;
118     protected String password;
119     // connection state
120     protected State state = State.INITIAL;
121     // Exchange session proxy
122     protected ExchangeSession session;
123 
124     /**
125      * Only set the thread name and socket
126      *
127      * @param name         thread type name
128      * @param clientSocket client socket
129      */
130     public AbstractConnection(String name, Socket clientSocket) {
131         super(name + '-' + clientSocket.getPort());
132         this.client = clientSocket;
133         setDaemon(true);
134     }
135 
136     /**
137      * Initialize the streams and set a thread name.
138      *
139      * @param name         thread type name
140      * @param clientSocket client socket
141      * @param encoding     socket stream encoding
142      */
143     public AbstractConnection(String name, Socket clientSocket, String encoding) {
144         super(name + '-' + clientSocket.getPort());
145         this.client = clientSocket;
146         logConnection("CONNECT", "");
147         try {
148             in = new LineReaderInputStream(client.getInputStream(), encoding);
149             os = new BufferedOutputStream(client.getOutputStream());
150         } catch (IOException e) {
151             close();
152             DavGatewayTray.error(new BundleMessage("LOG_EXCEPTION_GETTING_SOCKET_STREAMS"), e);
153         }
154     }
155 
156     public void logConnection(String action, String userName) {
157         Logger.getLogger("davmail.connection").info(action+" - "+client.getInetAddress().getHostAddress()+":"+client.getPort()+" " + userName);
158     }
159 
160     /**
161      * Send the provided message to the client followed by CRLF.
162      *
163      * @param message message
164      * @throws IOException on error
165      */
166     public void sendClient(String message) throws IOException {
167         sendClient(null, message);
168     }
169 
170     /**
171      * Send prefix and message to the client followed by CRLF.
172      *
173      * @param prefix  prefix
174      * @param message message
175      * @throws IOException on error
176      */
177     public void sendClient(String prefix, String message) throws IOException {
178         if (prefix != null) {
179             os.write(prefix.getBytes(StandardCharsets.UTF_8));
180             DavGatewayTray.debug(new BundleMessage("LOG_SEND_CLIENT_PREFIX_MESSAGE", prefix, message));
181         } else {
182             DavGatewayTray.debug(new BundleMessage("LOG_SEND_CLIENT_MESSAGE", message));
183         }
184         os.write(message.getBytes(StandardCharsets.UTF_8));
185         os.write((char) 13);
186         os.write((char) 10);
187         os.flush();
188     }
189 
190     /**
191      * Send all bytes to the client.
192      *
193      * @param messageBytes content
194      * @throws IOException on error
195      */
196     public void sendClient(byte[] messageBytes) throws IOException {
197         sendClient(messageBytes, 0, messageBytes.length);
198     }
199 
200     /**
201      * Send only bytes to the client.
202      *
203      * @param messageBytes content
204      * @param offset       the start offset in the data.
205      * @param length       the number of bytes to write.
206      * @throws IOException on error
207      */
208     public void sendClient(byte[] messageBytes, int offset, int length) throws IOException {
209         os.write(messageBytes, offset, length);
210         os.flush();
211     }
212 
213     /**
214      * Read a line from the client connection.
215      * Log message to logger
216      *
217      * @return command line or null
218      * @throws IOException when unable to read the next line
219      */
220     public String readClient() throws IOException {
221         String line = in.readLine();
222         if (line != null) {
223             if (line.startsWith("PASS")) {
224                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_PASS"));
225                 // SMTP LOGIN
226             } else if (line.startsWith("AUTH LOGIN ")) {
227                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTH_LOGIN"));
228                 // IMAP LOGIN
229             } else if (state == State.INITIAL && line.indexOf(' ') >= 0 &&
230                     line.substring(line.indexOf(' ') + 1).toUpperCase().startsWith("LOGIN")) {
231                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_LOGIN"));
232             } else if (state == State.PASSWORD) {
233                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_PASSWORD"));
234                 // HTTP Basic Authentication
235             } else if (line.startsWith("Authorization:")) {
236                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTHORIZATION"));
237             } else if (line.startsWith("AUTH PLAIN")) {
238                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_AUTH_PLAIN"));
239             } else {
240                 DavGatewayTray.debug(new BundleMessage("LOG_READ_CLIENT_LINE", line));
241             }
242         }
243         DavGatewayTray.switchIcon();
244         return line;
245     }
246 
247     /**
248      * Close client connection, streams, and Exchange session.
249      */
250     public void close() {
251         logConnection("DISCONNECT", "");
252         if (in != null) {
253             try {
254                 in.close();
255             } catch (IOException e2) {
256                 // ignore
257             }
258         }
259         if (os != null) {
260             try {
261                 os.close();
262             } catch (IOException e2) {
263                 // ignore
264             }
265         }
266         try {
267             client.close();
268         } catch (IOException e2) {
269             DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_SOCKET"), e2);
270         }
271     }
272 }