001: /*
002: *
003: * Jsmtpd, Java SMTP daemon
004: * Copyright (C) 2005-2006 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:
022: package org.jsmtpd.plugins.deliveryServices;
023:
024: import java.io.File;
025: import java.io.FileNotFoundException;
026: import java.io.IOException;
027: import java.io.RandomAccessFile;
028: import java.util.List;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.jsmtpd.config.ReadConfig;
033: import org.jsmtpd.core.common.PluginInitException;
034: import org.jsmtpd.core.common.delivery.FatalDeliveryException;
035: import org.jsmtpd.core.common.delivery.IDeliveryService;
036: import org.jsmtpd.core.common.delivery.TemporaryDeliveryException;
037: import org.jsmtpd.core.mail.Email;
038: import org.jsmtpd.core.mail.Rcpt;
039: import org.jsmtpd.tools.ByteArrayTool;
040: import org.jsmtpd.tools.rights.IChown;
041: import org.jsmtpd.tools.rights.RightException;
042: import org.jsmtpd.tools.rights.UnixChown;
043:
044: /**
045: * This has been tested with solid-pop3d, under linux.
046: * There is nothing in java to change the owner of a file (as far as I know ;),
047: * but some (many) pop/imap daemons working with mdir requires permissions to be set.
048: * The current impl to change right invokes a system command (fork process ...), it's not
049: * really beautifull, you have to run Jsmtpd as root, but it works.
050: *
051: * Don't use in production/stressed environment, behavior should not be good.
052: *
053: * @author Jean-Francois POUX
054: *
055: */
056: public class MaildirWriter implements IDeliveryService {
057: private String basePath = "/var/mail";
058: private String postPath = "";
059: private String host = ReadConfig.getInstance().getLocalDomain();
060: private Log log = LogFactory.getLog(MaildirWriter.class);
061: private IChown chown = new UnixChown();
062: private boolean tryChown = false;
063: private boolean crlf = false;
064:
065: public void setCrlf(boolean crlf) {
066: this .crlf = crlf;
067: }
068:
069: public void setChown(IChown chown) {
070: this .chown = chown;
071: }
072:
073: public void doDelivery(Email in, List<Rcpt> rcpts) {
074: log.debug("Starting batch");
075: for (Rcpt rcpt : rcpts) {
076: try {
077: deliver(in, rcpt);
078: rcpt.setDelivered(Rcpt.STATUS_DELIVERED);
079: log.debug("mail delivered to "
080: + rcpt.getEmailAddress().toString());
081: } catch (FatalDeliveryException e) {
082: rcpt.setDelivered(Rcpt.STATUS_ERROR_FATAL);
083: log.debug("fatal delivery error for "
084: + rcpt.getEmailAddress().toString());
085: } catch (TemporaryDeliveryException e) {
086: rcpt.setDelivered(Rcpt.STATUS_ERROR_NOT_FATAL);
087: log.debug("temporary delivery error for "
088: + rcpt.getEmailAddress().toString());
089: } catch (RightException e) {
090: log.debug("Fatal delivery error for rcpt (right error)"
091: + rcpt.getEmailAddress().toString());
092: rcpt.setDelivered(Rcpt.STATUS_ERROR_FATAL);
093: }
094: }
095: log.debug("ending batch");
096: }
097:
098: public String getPluginName() {
099: return "Maildir Writer for Jsmtpd";
100: }
101:
102: public void initPlugin() throws PluginInitException {
103: File fl = new File(basePath);
104: fl.mkdir();
105: }
106:
107: public void shutdownPlugin() {
108: }
109:
110: private void deliver(Email in, Rcpt rcpt)
111: throws TemporaryDeliveryException, FatalDeliveryException,
112: RightException {
113: String user = rcpt.getEmailAddress().getUser().toLowerCase(); // This assumes that a rcpt rewriter plugin exists for handling aliases, and rewrites to system uids.
114:
115: // Make sure dirs exists
116: String mpath = basePath + "/"
117: + rcpt.getEmailAddress().getUser() + "/" + postPath
118: + "/";
119: File fl = new File(mpath);
120: // if no exists mkdirs + rchown
121: if (!fl.exists()) {
122: fl.mkdirs();
123: if (tryChown)
124: chown.recursiveChown(fl.toString(), user);
125: }
126: File tmp = new File(mpath + "/tmp/");
127: if (!tmp.exists()) {
128: tmp.mkdirs();
129: if (tryChown)
130: chown.recursiveChown(tmp.toString(), user);
131: }
132: File cur = new File(mpath + "/cur/");
133: if (!cur.exists()) {
134: cur.mkdirs();
135: if (tryChown)
136: chown.recursiveChown(cur.toString(), user);
137: }
138: File newF = new File(mpath + "/new/");
139: if (!newF.exists()) {
140: newF.mkdirs();
141: if (tryChown)
142: chown.chown(newF.toString(), user);
143: }
144:
145: // Write to tmp directory
146: String fileName = makeUid();
147: File target = new File(tmp.getAbsolutePath() + "/" + fileName);
148: RandomAccessFile raf;
149: try {
150: raf = new RandomAccessFile(target, "rw");
151: raf.seek(0);
152: //DataStreamParser dsp = new DataStreamParser(512, 512);
153: //dsp.appendString("From " + in.getFrom().toString() + " " + DateUtil.currentMailboxDate());
154: //raf.write(ByteArrayTool.replaceBytes(dsp.getData(), ByteArrayTool.CRLF, ByteArrayTool.LF));
155: if (crlf) {
156: raf.write(ByteArrayTool.replaceBytes(
157: in.getDataAsByte(), ByteArrayTool.LF,
158: ByteArrayTool.CRLF));
159: byte[] lf = new byte[2];
160: lf[0] = 13;
161: lf[1] = 10;
162: raf.write(lf);
163: } else {
164: raf.write(ByteArrayTool.replaceBytes(
165: in.getDataAsByte(), ByteArrayTool.CRLF,
166: ByteArrayTool.LF));
167: byte[] lf = new byte[1];
168: lf[0] = 10;
169: raf.write(lf);
170: }
171:
172: raf.close();
173: } catch (FileNotFoundException e) {
174: log.error("File not found: " + e);
175: throw new TemporaryDeliveryException(e);
176: } catch (IOException e) {
177: log.error("Io error ", e);
178: throw new TemporaryDeliveryException(e);
179: }
180:
181: if (tryChown)
182: chown.chown(target.toString(), user);
183:
184: // Commit = mv
185: File commit = new File(cur.getAbsolutePath() + "/" + fileName);
186: target.renameTo(commit);
187: }
188:
189: /**
190: * See spec of D. J. Bernstein and what we can do in Java
191: * @return
192: */
193: private String makeUid() {
194: return "" + System.currentTimeMillis() + "."
195: + Thread.currentThread().getId() + "." + host;
196: }
197:
198: /**
199: * The path is made of basePath/user/postPath/
200: * @param basePath maildir storage prefix
201: */
202: public void setBasePath(String basePath) {
203: this .basePath = basePath;
204: }
205:
206: /**
207: * The path is made of basePath/user/postPath/
208: * @param postPath path suffix.
209: */
210: public void setPostPath(String postPath) {
211: this .postPath = postPath;
212: }
213:
214: public void setTryChown(boolean tryChown) {
215: this.tryChown = tryChown;
216: }
217:
218: }
|