001: /*
002: *
003: * Jsmtpd, Java SMTP daemon
004: * Copyright (C) 2005 Jean-Francois POUX, jf.poux@laposte.net
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License
008: * as published by the Free Software Foundation; either version 2
009: * of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: */
021: package org.jsmtpd.plugins.deliveryServices;
022:
023: import java.io.File;
024: import java.io.FileNotFoundException;
025: import java.io.IOException;
026: import java.io.RandomAccessFile;
027: import java.nio.channels.FileChannel;
028: import java.nio.channels.FileLock;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.List;
032: import java.util.Map;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.jsmtpd.core.common.PluginInitException;
037: import org.jsmtpd.core.common.delivery.FatalDeliveryException;
038: import org.jsmtpd.core.common.delivery.IDeliveryService;
039: import org.jsmtpd.core.common.delivery.TemporaryDeliveryException;
040: import org.jsmtpd.core.common.io.InvalidStreamParserInitialisation;
041: import org.jsmtpd.core.common.io.dataStream.DataStreamParser;
042: import org.jsmtpd.core.mail.Email;
043: import org.jsmtpd.core.mail.EmailAddress;
044: import org.jsmtpd.core.mail.Rcpt;
045: import org.jsmtpd.tools.ByteArrayTool;
046: import org.jsmtpd.tools.DateUtil;
047: import org.jsmtpd.tools.rights.IChown;
048: import org.jsmtpd.tools.rights.RightException;
049: import org.jsmtpd.tools.rights.UnixChown;
050:
051: /**
052: * Re implemented (23/07/2005), should work as first intended
053: * Multiple write against different files
054: * Now uses FileLock + a Map to maintain lock states
055: * @author Jean-Francois POUX
056: */
057: public class UnixMailboxWriter implements IDeliveryService {
058: private Log log = LogFactory.getLog(UnixMailboxWriter.class);
059: private String mailboxDir = null;
060: private Map<String, FileLock> locks = Collections
061: .synchronizedMap(new HashMap<String, FileLock>());
062: private boolean tryChown = false;
063: private IChown chown = new UnixChown();
064:
065: public void setTryChown(boolean tryChown) {
066: this .tryChown = tryChown;
067: }
068:
069: public void doDelivery(Email in, List<Rcpt> rcpts) {
070: log.debug("Begin ");
071: for (Rcpt rcpt : rcpts) {
072: try {
073: doSingleDelivery(in, rcpt.getEmailAddress());
074: rcpt.setDelivered(Rcpt.STATUS_DELIVERED);
075: } catch (FatalDeliveryException e) {
076: rcpt.setDelivered(Rcpt.STATUS_ERROR_FATAL);
077: } catch (TemporaryDeliveryException e) {
078: rcpt.setDelivered(Rcpt.STATUS_ERROR_NOT_FATAL);
079: }
080: }
081: log.debug("end");
082: }
083:
084: /**
085: * Delivers to one box
086: * @param in the message
087: * @param rcpt one recipient
088: * @throws FatalDeliveryException
089: * @throws TemporaryDeliveryException
090: */
091: public void doSingleDelivery(Email in, EmailAddress rcpt)
092: throws FatalDeliveryException, TemporaryDeliveryException {
093: RandomAccessFile fp = null;
094: String mailbox = mailboxDir + "/"
095: + rcpt.getUser().toLowerCase();
096: boolean exist = new File(mailbox).exists();
097: try {
098: fp = openMBox(mailbox);
099: //File is locked
100: log.debug("Locked mailbox: " + rcpt.getUser());
101: fp.seek(fp.length());
102: try {
103: DataStreamParser dsp = new DataStreamParser(512, 512);
104: dsp.appendString("From " + in.getFrom().toString()
105: + " " + DateUtil.currentMailboxDate());
106: fp.write(ByteArrayTool.replaceBytes(dsp.getData(),
107: ByteArrayTool.CRLF, ByteArrayTool.LF));
108: } catch (InvalidStreamParserInitialisation e1) {
109: }
110:
111: byte[] pattern = { '\n', 'F', 'r', 'o', 'm', ' ' };
112: byte[] replace = { '\n', '>', 'F', 'r', 'o', 'm', ' ' };
113: //convert crlf to lf (unix text files)
114: byte[] tempo = ByteArrayTool.replaceBytes(in
115: .getDataAsByte(), ByteArrayTool.CRLF,
116: ByteArrayTool.LF);
117: //preserve from :
118: tempo = ByteArrayTool.replaceBytes(tempo, pattern, replace);
119: fp.write(tempo);
120:
121: //apend <lf>
122: byte[] lf = new byte[1];
123: lf[0] = 10;
124: fp.write(lf);
125: if ((!exist) && tryChown)
126: chown.chown(mailbox, rcpt.getUser().toLowerCase());
127: //File is unlocked in the finally
128: log.debug("Mail " + in.getDiskName() + " delivered to : "
129: + rcpt.getUser());
130: }
131:
132: catch (TemporaryDeliveryException tde) {
133: log.info("Could not open/lock local mailbox: "
134: + rcpt.getUser() + ", delivery delayed");
135: throw tde;
136: }
137:
138: catch (IOException e) {
139: //Disk full, disk error, quota, etc => fatal error for this rcpt
140: log.info("IO Error delivering mail from "
141: + in.getFrom().toString() + " to mailbox "
142: + rcpt.toString() + ", mail to this rcpt is lost",
143: e);
144: throw new FatalDeliveryException();
145: } catch (RightException e) {
146: log
147: .error(
148: "Things are written, but I could not perfom chown on file ...",
149: e);
150: } finally {
151: closeMBox(mailbox, fp);
152: }
153: }
154:
155: private void goSleep() {
156: try {
157: Thread.sleep(1000);
158: } catch (InterruptedException e) {
159: }
160: }
161:
162: public String getPluginName() {
163: return "UnixMailboxWriter plugin for jsmtpd";
164: }
165:
166: /*
167: * (non-Javadoc)
168: *
169: * @see smtpd.common.IGenericPlugin#initPlugin()
170: */
171: public void initPlugin() throws PluginInitException {
172:
173: }
174:
175: /* (non-Javadoc)
176: * @see jsmtpd.common.IGenericPlugin#shutdownPlugin()
177: */
178: public void shutdownPlugin() {
179:
180: }
181:
182: //Autoconf
183: public void setMailDir(String mailboxesDirectory) {
184: this .mailboxDir = mailboxesDirectory;
185: }
186:
187: /**
188: * Opens the RandomAccesFile of the mailbox, and ensures its locked system wide (the file lock) and in the vm (in the hashmap)
189: * @param fileName name of the mailbox (full path)
190: * @return
191: * @throws TemporaryDeliveryException if the mbox is no lockable
192: */
193: private synchronized RandomAccessFile openMBox(String fileName)
194: throws TemporaryDeliveryException {
195: RandomAccessFile target = null;
196: FileLock lock = null;
197: // If our lock contains the mbox...
198: if (locks.containsKey(fileName))
199: throw new TemporaryDeliveryException();
200: else {
201: locks.put(fileName, null);
202: try {
203: target = getRandomAccessFile(fileName);
204: lock = getLock(target.getChannel(), fileName);
205: if (!lock.isValid()) {
206: throw new TemporaryDeliveryException();
207: }
208: locks.put(fileName, lock);
209: } catch (TemporaryDeliveryException e) {
210: locks.remove(fileName);
211: throw e;
212: }
213: }
214: return target;
215: }
216:
217: /**
218: * Tries to acquire a lock on the given channel
219: * @param channel
220: * @param mbox
221: * @return
222: * @throws TemporaryDeliveryException after 10 failed tries
223: */
224: private FileLock getLock(FileChannel channel, String mbox)
225: throws TemporaryDeliveryException {
226: FileLock lock = null;
227: int tryCount = 0;
228: //Try to lock the file for io access
229: while (lock == null) {
230: try {
231: lock = channel.tryLock();
232: } catch (IOException e) {
233: lock = null;
234: }
235: if (lock == null) {
236: log.debug("Can't lock mailbox:" + mbox + " retrying");
237: tryCount++;
238: goSleep();
239: }
240: if (tryCount > 10)
241: throw new TemporaryDeliveryException();
242: }
243: return lock;
244: }
245:
246: /**
247: * Tries to open a mbox.
248: * @param mailbox
249: * @return
250: * @throws TemporaryDeliveryException
251: */
252: // Note: This is for win32, reports a File not found ex if the file is lock by another process
253: private RandomAccessFile getRandomAccessFile(String mailbox)
254: throws TemporaryDeliveryException {
255: RandomAccessFile fp = null;
256: int tryCount = 0;
257: while (fp == null) {
258: if (tryCount > 10)
259: throw new TemporaryDeliveryException();
260:
261: try {
262: tryCount++;
263: fp = new RandomAccessFile(mailbox, "rw");
264: } catch (FileNotFoundException fileNotFoundException) {
265: log
266: .debug("Can't open mailbox: " + mailbox
267: + "retrying");
268: }
269: if (fp == null)
270: goSleep();
271: }
272: return fp;
273: }
274:
275: private synchronized void closeMBox(String fileName,
276: RandomAccessFile fp) {
277: if (locks.containsKey(fileName)) {
278: FileLock lock = locks.get(fileName);
279: locks.remove(fileName);
280: if (lock != null) {
281: try {
282: lock.release();
283: log.debug("UNlocked mailbox: " + fileName);
284: } catch (IOException e) {
285: log
286: .error("Error Unlocking mailbox: "
287: + fileName, e);
288: }
289: } else
290: log.error("Lock is not a file lock ?! " + lock);
291: }
292: if (fp != null) {
293: try {
294: fp.close();
295: } catch (IOException e) {
296: log.error("Error closing File ", e);
297: }
298: }
299: }
300: }
|