Source code for /engineering/autohit-2003/src/autohit/call/modules/TolerantSmtpModule.javaOriginal file TolerantSmtpModule.java
   1 /**
   2  * AUTOHIT 2003
   3  * Copyright Erich P Gatejen (c) 1989,1997,2003,2004
   4  * 
   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; either version 2 of the License, or (at
   8  * your option) any later version.
   9  * This program is distributed in the hope that it will be useful, but
  10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12  * more details.
  13  * 
  14  * You should have received a copy of the GNU General Public License along
  15  * with this program; if not, write to the Free Software Foundation, Inc.,
  16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17  *
  18  * Additional license information can be found in the documentation.
  19  * @author Erich P Gatejen
  20  */
  21 package autohit.call.modules;
  22 
  23 import java.io.BufferedReader;
  24 import java.io.InputStream;
  25 import java.io.InputStreamReader;
  26 import java.io.OutputStream;
  27 import java.io.Writer;
  28 
  29 import org.apache.commons.net.io.Util;
  30 import org.apache.commons.net.smtp.RelayPath;
  31 import org.apache.commons.net.smtp.SMTPClient;
  32 import org.apache.commons.net.smtp.SMTPConnectionClosedException;
  33 
  34 import autohit.call.CallException;
  35 import autohit.common.Constants;
  36 import autohit.universe.UniverseException;
  37 import autohit.vm.process.StringProcessors;
  38 
  39 /**
  40  * Tolerant SMTP module. There is a client/per module at this time. This one
  41  * supports streaming and will not throw faults on every error. <code>
  42  * start(address,optional{port}) start an SMTP session<br>
  43  * login(optional{hostname}) login to peer<br>
  44  * sender(address) set the sender with address<br>
  45  * addsenderrelay(address) add to the sender relay path<br>
  46  * senderrelay() set the sender with sender relay path.  clear the accumulated relay path.<br>
  47  * recipient(address) add a recipient with address<br>
  48  * addrecipientrelay(address) add to the recipient relay path<br>
  49  * newrecipientrelay(address) start a new recipient relay path<br>
  50  * recipientrelay() add a recipient with recipient relay path.  clear the accumulated relay path.<br>
  51  * send(text) send message from text.<br>
  52  * senduni(uniobj) send message from universe object.<br>
  53  * senduniscrub(uniobj) send message from universe object.  scrub it first with variable replacements<br>
  54  * reset() reset the smtp state.<br>
  55  * done() complete a session.  It will logout and close.<br>
  56  * mailit(from,to,text,host,optional{port}) convenience method for sending small message.
  57  * mailituni(from,to,uniobj,host,optional{port}) convenience method for sending small message.
  58  * </code>
  59  * 
  60  * @author Erich P. Gatejen
  61  * @version 1.0 <i>Version History</i><code>EPG - Initial, branched from SimpleSmtpModule - 25 Dec03</code>
  62  */
  63 public class TolerantSmtpModule extends Module {
  64 
  65 	private final static String myNAME = "TolerantSmtp";
  66 	private final static String TEMPFILE = "temp/tsmtpscrub";
  67 
  68 	private final static String ERROR_STRING_FOR_FAILURE = "451";
  69 	private final static int SMTP_ERROR_THRESHOLD = 300;
  70 	// An SMTP error code
  71 
  72 	/**
  73 	 * METHODS
  74 	 */
  75 	private final static String method_START = "start";
  76 	private final static String method_START_1_ADDRESS = "address";
  77 	private final static String method_START_2_PORT = "port";
  78 	private final static String method_LOGIN = "login";
  79 	private final static String method_LOGIN_1_HOSTNAME = "hostname";
  80 	private final static String method_SENDER = "sender";
  81 	private final static String method_SENDER_1_ADDRESS = "address";
  82 	private final static String method_ADDSENDERRELAY = "addsenderrelay";
  83 	private final static String method_ADDSENDERRELAY_1_ADDRESS = "address";
  84 	private final static String method_SENDERRELAY = "senderrelay";
  85 	private final static String method_RECIPIENT = "recipient";
  86 	private final static String method_RECIPIENT_1_ADDRESS = "address";
  87 	private final static String method_ADDRECIPIENTRELAY = "addrecipientrelay";
  88 	private final static String method_NEWRECIPIENTRELAY = "newrecipientrelay";
  89 	private final static String method_ADDRECIPIENTRELAY_1_ADDRESS = "address";
  90 	private final static String method_RECIPIENTRELAY = "recipientrelay";
  91 	private final static String method_SEND = "send";
  92 	private final static String method_SEND_1_TEXT = "text";
  93 	private final static String method_SENDUNI = "senduni";
  94 	private final static String method_SENDUNI_1_TEXT = "uniobj";
  95 	private final static String method_SENDUNISCRUB = "senduniscrub";
  96 	private final static String method_SENDUNISCRUB_1_TEXT = "uniobj";
  97 	private final static String method_RESET = "reset";
  98 	private final static String method_DONE = "done";
  99 	private final static String method_MAILIT = "mailit";
 100 	private final static String method_MAILIT_1_TO = "to";
 101 	private final static String method_MAILIT_2_FROM = "from";
 102 	private final static String method_MAILIT_3_TEXT = "text";
 103 	private final static String method_MAILIT_4_HOSTNAME = "host";
 104 	private final static String method_MAILIT_5_PORT = "port";
 105 	private final static String method_MAILITUNI = "mailituni";
 106 	private final static String method_MAILITUNI_1_TO = "to";
 107 	private final static String method_MAILITUNI_2_FROM = "from";
 108 	private final static String method_MAILITUNI_3_UNIOBJ = "uniobj";
 109 	private final static String method_MAILITUNI_4_HOSTNAME = "host";
 110 	private final static String method_MAILITUNI_5_PORT = "port";
 111 
 112 	SMTPClient client;
 113 	RelayPath senderrelay;
 114 	RelayPath recipientrelay;
 115 	boolean loggedIn;
 116 
 117 	/**
 118 	 * Constructor
 119 	 */
 120 	public TolerantSmtpModule() {
 121 
 122 	}
 123 
 124 	// IMPLEMENTORS
 125 
 126 	/**
 127 	 * Execute a named method. You must implement this method. You can call any
 128 	 * of the helpers for data and services. The returned object better be a
 129 	 * string (for now).
 130 	 * 
 131 	 * @param name
 132 	 *            name of the method
 133 	 * @see autohit.common.NOPair
 134 	 * @throws CallException
 135 	 */
 136 	public Object execute_chain(String name) throws CallException {
 137 
 138 		Object response = Constants.EMPTY_LEFT;
 139 		Object thingie;
 140 
 141 		if (name.equals(method_START)) {
 142 			String param1 = this.required(method_START_1_ADDRESS, name);
 143 			String param2 = this.optional(method_START_2_PORT);
 144 			this.start(param1, param2);
 145 
 146 		} else if (name.equals(method_LOGIN)) {
 147 			String param1 = this.optional(method_LOGIN_1_HOSTNAME);
 148 			this.login(param1);
 149 
 150 		} else if (name.equals(method_SENDER)) {
 151 			String param1 = this.required(method_SENDER_1_ADDRESS, name);
 152 			this.sender(param1);
 153 
 154 		} else if (name.equals(method_ADDSENDERRELAY)) {
 155 			String param1 =
 156 				this.required(method_ADDSENDERRELAY_1_ADDRESS, name);
 157 			this.addsenderrelay(param1);
 158 
 159 		} else if (name.equals(method_SENDERRELAY)) {
 160 			this.senderrelay();
 161 
 162 		} else if (name.equals(method_RECIPIENT)) {
 163 			String param1 = this.required(method_RECIPIENT_1_ADDRESS, name);
 164 			this.recipient(param1);
 165 
 166 		} else if (name.equals(method_ADDRECIPIENTRELAY)) {
 167 			String param1 =
 168 				this.required(method_ADDRECIPIENTRELAY_1_ADDRESS, name);
 169 			this.addrecipientrelay(param1);
 170 
 171 		} else if (name.equals(method_NEWRECIPIENTRELAY)) {
 172 			this.newrecipientrelay();
 173 
 174 		} else if (name.equals(method_RECIPIENTRELAY)) {
 175 			this.recipientrelay();
 176 
 177 		} else if (name.equals(method_SEND)) {
 178 			String param1 = this.required(method_SEND_1_TEXT, name);
 179 			response = this.send(param1);
 180 
 181 		} else if (name.equals(method_SENDUNI)) {
 182 			String param1 = this.required(method_SENDUNI_1_TEXT, name);
 183 			response = this.senduni(param1);
 184 
 185 		} else if (name.equals(method_SENDUNISCRUB)) {
 186 			String param1 = this.required(method_SENDUNISCRUB_1_TEXT, name);
 187 			response = this.senduniscrub(param1);
 188 
 189 		} else if (name.equals(method_RESET)) {
 190 			this.reset();
 191 
 192 		} else if (name.equals(method_DONE)) {
 193 			this.done();
 194 
 195 		} else if (name.equals(method_MAILIT)) {
 196 			String param1 = this.required(method_MAILIT_1_TO, name);
 197 			String param2 = this.required(method_MAILIT_2_FROM, name);
 198 			String param3 = this.required(method_MAILIT_3_TEXT, name);
 199 			String param4 = this.required(method_MAILIT_4_HOSTNAME, name);
 200 			String param5 = this.optional(method_MAILIT_5_PORT);
 201 			response = this.mailit(param1, param2, param3, param4, param5);
 202 
 203 		} else if (name.equals(method_MAILITUNI)) {
 204 			String param1 = this.required(method_MAILITUNI_1_TO, name);
 205 			String param2 = this.required(method_MAILITUNI_2_FROM, name);
 206 			String param3 = this.required(method_MAILITUNI_3_UNIOBJ, name);
 207 			String param4 = this.required(method_MAILITUNI_4_HOSTNAME, name);
 208 			String param5 = this.optional(method_MAILITUNI_5_PORT);
 209 			response = this.mailituni(param1, param2, param3, param4, param5);
 210 
 211 		} else {
 212 			error("Not a provided method.  method=" + name);
 213 		}
 214 		return response;
 215 	}
 216 
 217 	/**
 218 	 * Allow the subclass a chance to initialize. At a minium, an implementor
 219 	 * should create an empty method.
 220 	 * 
 221 	 * @throws CallException
 222 	 * @return the name
 223 	 */
 224 	protected String instantiation_chain() throws CallException {
 225 		client = null;
 226 		loggedIn = false;
 227 		return myNAME;
 228 	}
 229 
 230 	/**
 231 	 * Allow the subclass a chance to cleanup on free. At a minium, an
 232 	 * implementor should create an empty method.
 233 	 * 
 234 	 * @throws CallException
 235 	 */
 236 	protected void free_chain() throws CallException {
 237 		try {
 238 			this.done();
 239 		} catch (Exception e) {
 240 			// don't care
 241 		}
 242 	}
 243 
 244 	// PRIVATE IMPLEMENTATIONS
 245 
 246 	/**
 247 	 * Start method. It will open a connection to an SMTP server/relay. If a
 248 	 * session is already started, it will report an error. If it cannot make
 249 	 * the connection, it will cause a fault.
 250 	 * 
 251 	 * @param addr
 252 	 *            the domain name address. Do not include protocol or port.
 253 	 * @param post
 254 	 *            this should be a parsable integer. If it is null, the default
 255 	 *            will be used.
 256 	 * @throws CallException
 257 	 */
 258 	private void start(String addr, String port) throws CallException {
 259 
 260 		SMTPClient candidate = null;
 261 
 262 		// Already started?
 263 		if (client != null) {
 264 			this.error("Session already started.  Ignoring new start().");
 265 			return;
 266 		}
 267 		// Passed a port number?
 268 		int portNum = 0;
 269 		if (port != null) {
 270 			try {
 271 				portNum = Integer.parseInt(port);
 272 			} catch (Exception e) {
 273 				this.fault(
 274 					"Malformed 'port' number.  It must be a parsable integer.  text="
 275 						+ port);
 276 			}
 277 		}
 278 		// Try and construct it
 279 		try {
 280 			candidate = new SMTPClient();
 281 			if (port == null) {
 282 				candidate.connect(addr);
 283 			} else {
 284 				candidate.connect(addr, portNum);
 285 			}
 286 
 287 		} catch (Exception ex) {
 288 			this.error(
 289 				"Could not connect to host.  message=" + ex.getMessage());
 290 			return;
 291 		}
 292 
 293 		// NO CODE AFTER THIS!
 294 		this.log("Connection started.");
 295 		loggedIn = false;
 296 		client = candidate;
 297 	}
 298 
 299 	/**
 300 	 * Done method. Dispose of state and everything.
 301 	 */
 302 	private void done() {
 303 
 304 		// Brute force close. Don't care about errors
 305 		if (client != null) {
 306 			try {
 307 				client.logout();
 308 			} catch (Exception exx) {
 309 			}
 310 			try {
 311 				client.disconnect();
 312 			} catch (Exception exx) {
 313 			}
 314 		}
 315 		senderrelay = null;
 316 		client = null;
 317 		loggedIn = false;
 318 		this.log("Connection closed.");
 319 		// NO NEW CODE BEFORE THIS LINE!
 320 	}
 321 
 322 	/**
 323 	 * Login method. It will fault if a session has not been started or has
 324 	 * expired. The hostname is optional; pass null if not used.
 325 	 * 
 326 	 * @param hostname
 327 	 *            hostname to use instead of localhost.
 328 	 * @throws CallException
 329 	 */
 330 	private void login(String hostname) throws CallException {
 331 
 332 		// Is it started?
 333 		if (client == null) {
 334 			this.error("Session not start()'ed.");
 335 		}
 336 
 337 		try {
 338 			if (hostname == null) {
 339 				client.login();
 340 			} else {
 341 				client.login(hostname);
 342 			}
 343 
 344 			// what happened?
 345 			int code = client.getReplyCode();
 346 			if (code >= SMTP_ERROR_THRESHOLD) {
 347 				this.error(
 348 					"Login failed with code="
 349 						+ code
 350 						+ " reply="
 351 						+ client.getReplyString());
 352 			} else {
 353 				loggedIn = true;
 354 				if (this.isDebugging()) {
 355 					this.debug("Login complete.  code=" + code);
 356 				}
 357 			}
 358 
 359 		} catch (SMTPConnectionClosedException ex) {
 360 			this.done();
 361 			this.error("Cannot login.  Connection expired and closed itself.");
 362 		} catch (Exception ex) {
 363 			this.done();
 364 			this.error(
 365 				"Cannot login due to exception.  message=" + ex.getMessage());
 366 		}
 367 	}
 368 
 369 	/**
 370 	 * Sender method. It will fault if a session has not been started or has
 371 	 * expired. It will set the sender. Subsequent calls will overwrite the
 372 	 * value. The address should be a valid email address.
 373 	 * 
 374 	 * @param hostname
 375 	 *            hostname to use instead of localhost.
 376 	 * @throws CallException
 377 	 */
 378 	private void sender(String s) throws CallException {
 379 
 380 		// Is it started?
 381 		if (client == null) {
 382 			this.error("Session not start()'ed.");
 383 		}
 384 
 385 		try {
 386 			client.setSender(s);
 387 
 388 			// what happened?
 389 			int code = client.getReplyCode();
 390 			if (code >= SMTP_ERROR_THRESHOLD) {
 391 				this.error(
 392 					"Sender failed with code="
 393 						+ code
 394 						+ " reply="
 395 						+ client.getReplyString());
 396 			} else if (this.isDebugging()) {
 397 				this.debug("Sender complete.  code=" + code);
 398 			}
 399 
 400 		} catch (SMTPConnectionClosedException ex) {
 401 			this.done();
 402 			this.error(
 403 				"Cannot set sender.  Connection expired and closed itself.");
 404 		} catch (Exception ex) {
 405 			this.done();
 406 			this.error(
 407 				"Cannot set sender due to exception.  message="
 408 					+ ex.getMessage());
 409 		}
 410 	}
 411 
 412 	/**
 413 	 * Add sender relay leg method. It will fault if a session has not been
 414 	 * started or has expired. It will start accumulating a sender relay path.
 415 	 * You must call this at least once, if you are going to use senderrelay().
 416 	 * The first call should contain a complete email address at the root of
 417 	 * the relay chain. The accumulation will be reset after done() or other
 418 	 * connection ending event.
 419 	 * 
 420 	 * @param leg
 421 	 *            relay leg.
 422 	 * @throws CallException
 423 	 */
 424 	private void addsenderrelay(String leg) throws CallException {
 425 
 426 		// Is it started?
 427 		if (client == null) {
 428 			this.fault("Session not start()'ed.");
 429 		}
 430 
 431 		try {
 432 			if (senderrelay == null) {
 433 				senderrelay = new RelayPath(leg);
 434 			} else {
 435 				senderrelay.addRelay(leg);
 436 			}
 437 
 438 		} catch (Exception ex) {
 439 			this.done();
 440 			this.error(
 441 				"Cannot add sender relay due to exception.  message="
 442 					+ ex.getMessage());
 443 		}
 444 	}
 445 
 446 	/**
 447 	 * Sender relay method. It will fault if a session has not been started or
 448 	 * has expired. It will set the sender as the accumulated relay. You must
 449 	 * have called addsenderrelay() at least once or you will get an error.
 450 	 * Subsequent calls will overwrite the value.
 451 	 * 
 452 	 * @param hostname
 453 	 *            hostname to use instead of localhost.
 454 	 * @throws CallException
 455 	 */
 456 	private void senderrelay() throws CallException {
 457 
 458 		// Is it started?
 459 		if (client == null) {
 460 			this.error("Session not start()'ed.");
 461 		}
 462 
 463 		// Is there a relay?
 464 		if (senderrelay == null) {
 465 			this.error(
 466 				"Sender relay not ready.  You need to call addsenderrelay() at least once before this method.");
 467 			return;
 468 		}
 469 
 470 		try {
 471 			client.setSender(senderrelay);
 472 
 473 			// what happened?
 474 			int code = client.getReplyCode();
 475 			if (code >= SMTP_ERROR_THRESHOLD) {
 476 				this.error(
 477 					"Set sender relay failed with code="
 478 						+ code
 479 						+ " reply="
 480 						+ client.getReplyString());
 481 			} else if (this.isDebugging()) {
 482 				this.debug("Set sender relay complete.  code=" + code);
 483 			}
 484 
 485 		} catch (SMTPConnectionClosedException ex) {
 486 			this.done();
 487 			this.error(
 488 				"Cannot set sender.  Connection expired and closed itself.");
 489 		} catch (Exception ex) {
 490 			this.done();
 491 			this.error(
 492 				"Cannot set sender due to exception.  message="
 493 					+ ex.getMessage());
 494 		}
 495 	}
 496 
 497 	/**
 498 	 * Recipient method. It will fault if a session has not been started or has
 499 	 * expired. It will add a recipient. Subsequent calls will add to the list
 500 	 * of recipients. The address should be a valid email address. The only way
 501 	 * to clear the list is to call reset() and start over.
 502 	 * 
 503 	 * @param hostname
 504 	 *            hostname to use instead of localhost.
 505 	 * @throws CallException
 506 	 */
 507 	private void recipient(String s) throws CallException {
 508 
 509 		// Is it started?
 510 		if (client == null) {
 511 			this.error("Session not start()'ed.");
 512 		}
 513 
 514 		try {
 515 			client.addRecipient(s);
 516 
 517 			// what happened?
 518 			int code = client.getReplyCode();
 519 			if (code >= SMTP_ERROR_THRESHOLD) {
 520 				this.error(
 521 					"Add recipient failed with code="
 522 						+ code
 523 						+ " reply="
 524 						+ client.getReplyString());
 525 			} else if (this.isDebugging()) {
 526 				this.debug("Add recipient complete.  code=" + code);
 527 			}
 528 
 529 		} catch (SMTPConnectionClosedException ex) {
 530 			this.done();
 531 			this.error(
 532 				"Cannot add recipient.  Connection expired and closed itself.");
 533 		} catch (Exception ex) {
 534 			this.done();
 535 			this.error(
 536 				"Cannot add recipient due to exception.  message="
 537 					+ ex.getMessage());
 538 		}
 539 	}
 540 
 541 	/**
 542 	 * Add recipeint relay leg method. It will fault if a session has not been
 543 	 * started or has expired. It will start accumulating a recipeint relay
 544 	 * path. You must call this at least once, if you are going to use
 545 	 * recipient relay(). The first call should contain a complete email
 546 	 * address at the root of the relay chain. The accumulation will be reset
 547 	 * after done(), a call to new recipientrelay(), or some other connection
 548 	 * ending event.
 549 	 * 
 550 	 * @param leg
 551 	 *            relay leg.
 552 	 * @throws CallException
 553 	 */
 554 	private void addrecipientrelay(String leg) throws CallException {
 555 
 556 		// Is it started?
 557 		if (client == null) {
 558 			this.error("Session not start()'ed.");
 559 		}
 560 
 561 		try {
 562 			if (recipientrelay == null) {
 563 				recipientrelay = new RelayPath(leg);
 564 			} else {
 565 				recipientrelay.addRelay(leg);
 566 			}
 567 		} catch (Exception ex) {
 568 			this.done();
 569 			this.error(
 570 				"Cannot add recipient relay due to exception.  message="
 571 					+ ex.getMessage());
 572 		}
 573 	}
 574 
 575 	/**
 576 	 * Recipient relay method. It will fault if a session has not been started
 577 	 * or has expired. It will set the sender as the accumulated relay. You
 578 	 * must have called addrecipientrelay() at least once or you will get an
 579 	 * error. Subsequent calls will overwrite the value.
 580 	 * 
 581 	 * @param hostname
 582 	 *            hostname to use instead of localhost.
 583 	 * @throws CallException
 584 	 */
 585 	private void recipientrelay() throws CallException {
 586 
 587 		// Is it started?
 588 		if (client == null) {
 589 			this.error("Session not start()'ed.");
 590 		}
 591 
 592 		// Is there a relay?
 593 		if (recipientrelay == null) {
 594 			this.error(
 595 				"Recipient relay not ready.  You need to call addrecipientrelay() at least once before this method.");
 596 			return;
 597 		}
 598 
 599 		try {
 600 			client.addRecipient(recipientrelay);
 601 
 602 			// what happened?
 603 			int code = client.getReplyCode();
 604 			if (code >= SMTP_ERROR_THRESHOLD) {
 605 				this.error(
 606 					"Add recipient relay failed with code="
 607 						+ code
 608 						+ " reply="
 609 						+ client.getReplyString());
 610 			} else if (this.isDebugging()) {
 611 				this.debug("Add recipient relay complete.  code=" + code);
 612 			}
 613 
 614 		} catch (SMTPConnectionClosedException ex) {
 615 			this.done();
 616 			this.error(
 617 				"Cannot add recipient.  Connection expired and closed itself.");
 618 		} catch (Exception ex) {
 619 			this.done();
 620 			this.error(
 621 				"Cannot add recipient due to exception.  message="
 622 					+ ex.getMessage());
 623 		}
 624 	
 625 	}
 626 	
 627 	/**
 628 	 * Clear the recipeint relay accumulation. Do this if you want to start on
 629 	 * a new recipeint relay. This will never report any kind of error.
 630 	 * 
 631 	 * @param leg
 632 	 *            relay leg.
 633 	 * @throws CallException
 634 	 */
 635 	private void newrecipientrelay() {
 636 		recipientrelay = null;
 637 	}
 638 
 639 	/**
 640 	 * Send the message in the text. Returns the SMTP reply code.
 641 	 * 
 642 	 * @param hostname
 643 	 *            hostname to use instead of localhost.
 644 	 * @throws CallException
 645 	 */
 646 	private String send(String text) throws CallException {
 647 
 648 		String result = ERROR_STRING_FOR_FAILURE;
 649 
 650 		// Is it started?
 651 		if (client == null) {
 652 			this.error("Session not start()'ed.");
 653 		}
 654 
 655 		// Is there something to send? PARANOID
 656 		if (text == null) {
 657 			this.error("Nothing to send.");
 658 			return result;
 659 		}
 660 
 661 		try {
 662 
 663 			client.sendShortMessageData(text);
 664 			//client.completePendingCommand(); // don't care if it was ok.
 665 			result = Integer.toString(client.getReplyCode());
 666 
 667 			// what happened?
 668 			int code = client.getReplyCode();
 669 			if (code >= SMTP_ERROR_THRESHOLD) {
 670 				this.error(
 671 					"Message send FAILED.  code="
 672 						+ code
 673 						+ " reply="
 674 						+ client.getReplyString());
 675 			} else if (this.isDebugging()) {
 676 				this.debug(
 677 					"Message send complete.  code="
 678 						+ code
 679 						+ " reply="
 680 						+ client.getReplyString());
 681 			}
 682 
 683 		} catch (SMTPConnectionClosedException ex) {
 684 			this.done();
 685 			this.error("Send failed.  Connection expired and closed itself." + " last reply="
 686 					+ client.getReplyString());
 687 		} catch (Exception ex) {
 688 			this.done();
 689 			this.error(
 690 				"Send failed due to exception.  message=" + ex.getMessage());
 691 		} 
 692 		return result;
 693 	}
 694 
 695 	/**
 696 	 * Send the message in a Universe Object. Returns the SMTP reply code.
 697 	 * 
 698 	 * @param hostname
 699 	 *            hostname to use instead of localhost.
 700 	 * @throws CallException
 701 	 */
 702 	private String senduni(String uniobject) throws CallException {
 703 
 704 		String result = ERROR_STRING_FOR_FAILURE;
 705 
 706 		// Is it started?
 707 		if (client == null) {
 708 			this.error("Session not start()'ed.");
 709 		}
 710 
 711 		try {
 712 			// get the hoses
 713 			InputStream unio = visUniverse.getStream(uniobject);
 714 			BufferedReader bin =
 715 				new BufferedReader(new InputStreamReader(unio));
 716 			Writer mwriter = client.sendMessageData();
 717 
 718 			// and pipe them together
 719 			if (mwriter != null) {
 720 				Util.copyReader(bin, mwriter);
 721 				mwriter.close();
 722 				unio.close();
 723 				client.completePendingCommand(); // don't care if it was ok.
 724 
 725 				// what happened?
 726 				int code = client.getReplyCode();
 727 				if (code >= SMTP_ERROR_THRESHOLD) {
 728 					this.error(
 729 						"Message send FAILED (from Universe).  code="
 730 							+ code
 731 							+ " reply="
 732 							+ client.getReplyString());
 733 				} else if (this.isDebugging()) {
 734 					this.debug("Message send complete (from Universe).  code=");
 735 				}
 736 				result = Integer.toString(code);
 737 
 738 			} else {
 739 				this.log(
 740 					"Message send FAILED (from Universe) because SMTP connection was completely ready.  reply="
 741 						+ client.getReplyString());
 742 			}
 743 
 744 		} catch (UniverseException uex) {
 745 			this.fault(
 746 				"Could not send universe object due to Universe problem.  code="
 747 					+ uex.numeric
 748 					+ "  message="
 749 					+ uex.getMessage(),
 750 				uex);
 751 		} catch (SMTPConnectionClosedException ex) {
 752 			this.done();
 753 			this.error(
 754 				"Send failed (from Universe).  Connection expired and closed itself.  Last reply="
 755 					+ client.getReplyString());
 756 		} catch (Exception ex) {
 757 			this.done();
 758 			this.error(
 759 				"Send failed (from Universe) due to exception.  message="
 760 					+ ex.getMessage());
 761 		}
 762 		return result;
 763 	}
 764 
 765 	/**
 766 	 * Send the message in a Universe Object. Returns the SMTP reply code. It
 767 	 * will run a variable replace on it before sending it.
 768 	 * 
 769 	 * @param hostname
 770 	 *            hostname to use instead of localhost.
 771 	 * @throws CallException
 772 	 */
 773 	private String senduniscrub(String uniobject) throws CallException {
 774 
 775 		String result = ERROR_STRING_FOR_FAILURE;
 776 		String tempObj = null;
 777 
 778 		// Is it started?
 779 		if (client == null) {
 780 			this.error("Session not start()'ed.");
 781 		}
 782 
 783 		try {
 784 
 785 			// Process it
 786 			try {
 787 				tempObj = visUniverse.reserveUnique(TEMPFILE);
 788 				OutputStream os = visUniverse.putStream(tempObj);
 789 				StringProcessors.evalStreams2Core(visUniverse.getStream(uniobject), os, visCore);
 790 				os.close();
 791 
 792 			} catch (UniverseException ue) {
 793 				throw ue;
 794 			} catch (Exception e) {
 795 				this.fault(
 796 					"Send failed (from Universe).  Could not create scrubbed intermediary tempfile.  message="
 797 						+ e.getMessage());
 798 			}
 799 
 800 			// get the hoses
 801 			InputStream unio = visUniverse.getStream(tempObj);
 802 			BufferedReader bin =
 803 				new BufferedReader(new InputStreamReader(unio));
 804 			Writer mwriter = client.sendMessageData();
 805 
 806 			// and pipe them together
 807 			if (mwriter != null) {
 808 				Util.copyReader(bin, mwriter);
 809 				mwriter.close();
 810 				unio.close();
 811 				client.completePendingCommand(); // don't care if it was ok.
 812 
 813 				// what happened?
 814 				int code = client.getReplyCode();
 815 				if (code >= SMTP_ERROR_THRESHOLD) {
 816 					this.error(
 817 						"Message send FAILED (from Universe).  code="
 818 							+ code
 819 							+ " reply="
 820 							+ client.getReplyString());
 821 				} else if (this.isDebugging()) {
 822 					this.debug("Message send complete (from Universe).  code=");
 823 				}
 824 				result = Integer.toString(code);
 825 			
 826 			} else {
 827 				this.log(
 828 					"Message send FAILED (from Universe) because SMTP connection was completely ready.  reply="
 829 						+ client.getReplyString());
 830 			}
 831 
 832 		} catch (CallException cex) {
 833 			throw cex;
 834 		} catch (UniverseException uex) {
 835 			this.fault(
 836 				"Could not send universe object due to Universe problem.  code="
 837 					+ uex.numeric
 838 					+ "  message="
 839 					+ uex.getMessage(),
 840 				uex);
 841 		} catch (SMTPConnectionClosedException ex) {
 842 			this.done();
 843 			this.error(
 844 				"Send failed (from Universe).  Connection expired and closed itself.  Last reply="
 845 					+ client.getReplyString());
 846 		} catch (Exception ex) {
 847 			this.done();
 848 			this.error(
 849 				"Send failed (from Universe) due to exception.  message="
 850 					+ ex.getMessage());
 851 		} finally  {
 852 			if ((!this.isDebugging()) && (tempObj != null)) {
 853 				try {
 854 					visUniverse.remove(tempObj);
 855 				} catch (Exception ee) {
 856 					// Don't care
 857 				}
 858 			}
 859 		}
 860 		return result;
 861 	}
 862 
 863 	/**
 864 	 * Reset method. It will never give an error, even if not start()'ed.
 865 	 * 
 866 	 * @throws CallException
 867 	 */
 868 	private void reset() throws CallException {
 869 		try {
 870 			if (client != null)
 871 				client.reset();
 872 
 873 			// what happened?
 874 			int code = client.getReplyCode();
 875 			if (code >= SMTP_ERROR_THRESHOLD) {
 876 				this.error(
 877 					"Reset FAILED with code="
 878 						+ code
 879 						+ " reply="
 880 						+ client.getReplyString());
 881 				this.done();
 882 			} else if (this.isDebugging()) {
 883 				this.debug("Reset complete.  code=" + code);
 884 			}
 885 
 886 		} catch (Exception ex) {
 887 		}
 888 	}
 889 
 890 	/**
 891 	 * Mailit method. A complete transaction wrapped into a convenient method.
 892 	 * It will start a session using host/port parameters. If a session is
 893 	 * already started, host/port will be ignored and the current session will
 894 	 * be used.
 895 	 * 
 896 	 * @param to
 897 	 *            TO address.
 898 	 * @param from
 899 	 *            FROM address.
 900 	 * @param text
 901 	 *            to send as the message
 902 	 * @param host
 903 	 *            address of smpt server or relay
 904 	 * @param port
 905 	 *            optional port
 906 	 * @throws CallException
 907 	 */
 908 	private String mailit(
 909 		String to,
 910 		String from,
 911 		String text,
 912 		String host,
 913 		String port)
 914 		throws CallException {
 915 
 916 		String response = null;
 917 
 918 		// Is it started?
 919 		if (client == null) {
 920 			this.start(host, port);
 921 		}
 922 		
 923 		if (client == null) return "XXX";
 924 		
 925 		try {
 926 
 927 			if (loggedIn == false)
 928 				this.login(null);
 929 			this.reset();
 930 			this.sender(from);
 931 			this.recipient(to);
 932 			response = this.send(text);
 933 
 934 		} catch (CallException ce) {
 935 			this.done();
 936 			throw ce;
 937 		} catch (Exception ee) {
 938 			this.done();
 939 			this.fault("Serious exception caused FAULT while sending.  message=" + ee.getMessage());
 940 		}
 941 		return response;
 942 	}
 943 
 944 	/**
 945 	 * Mailituni method. A complete transaction wrapped into a convenient
 946 	 * method. It will start a session using host/port parameters. If a session
 947 	 * is already started, host/port will be ignored and the current session
 948 	 * will be used.
 949 	 * 
 950 	 * @param to
 951 	 *            TO address.
 952 	 * @param from
 953 	 *            FROM address.
 954 	 * @param uniobj
 955 	 *            to send as the message from universe
 956 	 * @param host
 957 	 *            address of smpt server or relay
 958 	 * @param port
 959 	 *            optional port
 960 	 * @throws CallException
 961 	 */
 962 	private String mailituni(
 963 		String to,
 964 		String from,
 965 		String uniobj,
 966 		String host,
 967 		String port)
 968 		throws CallException {
 969 
 970 		String response = null;
 971 
 972 		// Is it started?
 973 		if (client == null) {
 974 			this.start(host, port);
 975 		}
 976 
 977 		if (client == null) return "XXX";
 978 		
 979 		try {
 980 			if (loggedIn == false)
 981 				this.login(null);
 982 			this.reset();
 983 			this.sender(from);
 984 			this.recipient(to);
 985 			response = this.senduniscrub(uniobj);
 986 			
 987 		} catch (CallException ce) {
 988 			this.done();
 989 			throw ce;
 990 		} catch (Exception ee) {
 991 			this.done();
 992 			this.fault("Serious exception caused FAULT while sending.  message=" + ee.getMessage());
 993 		}
 994 		return response;
 995 	}
 996 }