1

I am running a java application from the console on an HP-UX machine. In it, I generate some reports, zip them, and then email them. Everything is working, except the email.

I am using the mail binary to send mail from the command line. Since it's HP-UX, it's a bit different than the standard GNU sendmail.

This is the code I'm using to send the mail:

    public static void EmailReports(String[] recipients, String reportArchive, String subject){
        SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
        String today = dateFormat.format(new Date());

        File tempEmailFile;
        BufferedWriter emailWriter;
        try {
            tempEmailFile = File.createTempFile("report_email_" + today, "msg");
            emailWriter = new BufferedWriter(new FileWriter(tempEmailFile));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Failed to send email. Could not create temporary file.");
            return;
        }

        try {
            emailWriter.write("SUBJECT: " + subject + "\n");
            emailWriter.write("FROM: " + FROM + "\n");
            emailWriter.write(BODY + "\n"); 
            emailWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Failed to send email. Could not write to temporary file.");
        }

        //read the archive in
        try {
            FileInputStream archiveIS = new FileInputStream(new File(reportArchive));
            OutputStream archiveEncoder = MimeUtility.encode(new FileOutputStream(tempEmailFile, true), "uuencode", Zipper.getArchiveName(reportArchive));

            //read archive
            byte[] buffer = new byte[archiveIS.available()];    //these should never be more than a megabyte or two, so storing it in memory is no big deal.
            archiveIS.read(buffer);

            //encode archive
            archiveEncoder.write(buffer);

            //close both
            archiveIS.close();
            archiveEncoder.close();

        } catch (FileNotFoundException e) {
            System.out.println("Failed to send email. Could not find archive to email.");
            e.printStackTrace();
        } catch (MessagingException e) {
            System.out.println("Failed to send email. Could not encode archive.");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Failed to send email. Could not encode archive.");
        }
        System.out.println("Sending '" + subject + "' email.");     

        try {
            Process p = Runtime.getRuntime().exec("mail me@example.com < " + tempEmailFile.getAbsolutePath());
            System.out.println("mail me@example.com < " + tempEmailFile.getAbsolutePath());

            StringBuffer buffer = new StringBuffer();
            while(p.getErrorStream().available() > 0){
                buffer.append((char) p.getErrorStream().read());
            }

            System.out.println("STDERR: " + buffer.toString());

            buffer = new StringBuffer();
            while(p.getInputStream().available() > 0){
                buffer.append((char) p.getInputStream().read());
            }

            System.out.println("STDOUT: " + buffer.toString());
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Failed to send email. Could not get access to the shell.");
        }
    }

When I run the program, and it sends the email, I get a blank email, no subject, no body, no attachment, and it's from the user@hostname from the HP-UX box instead of from the email specified in FROM.

However, when I run the same line that it runs (see the command printed out after I call exec), I get the correct email, from the correct user, with a subject, body, and attachment.

STDOUT and STDERR are both empty. It's almost as if I'm sending mail a blank file, but when I print the file before I call the exec, it's there.

What's going on here?

Edit: Attempts made:

Using Ksh:

    try {
        String cmd = "mail me@example.com.com < " + tempEmailFile.getAbsolutePath();            
        Runtime.getRuntime().exec(new String[] {"/usr/bin/ksh", cmd});

    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("Failed to send email. Could not get access to the shell.");
    }

Using STDIN:

    try {
        System.out.println("mail me@example.com < " + tempEmailFile.getAbsolutePath());

        Process p = Runtime.getRuntime().exec("mail me@example.com ");

        FileInputStream inFile = new FileInputStream(tempEmailFile);
        byte[] byteBuffer = new byte[inFile.available()];
        inFile.read(byteBuffer);
        p.getOutputStream().write(byteBuffer);

        inFile.close();
        p.getOutputStream().close();

        StringBuffer buffer = new StringBuffer();
        while(p.getErrorStream().available() > 0){
            buffer.append((char) p.getErrorStream().read());
        }

        System.out.println("STDERR: " + buffer.toString());

        buffer = new StringBuffer();
        while(p.getInputStream().available() > 0){
            buffer.append((char) p.getInputStream().read());
        }

        System.out.println("STDOUT: " + buffer.toString());
    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("Failed to send email. Could not get access to the shell.");
    }
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Malfist
  • 31,179
  • 61
  • 182
  • 269

3 Answers3

2

I strongly suspect the problem is the redirection. That's normally handled by the shell - and there's no shell here.

Either you need to execute the process normally and then get the process's standard input stream and write to it from Java, or (probably simpler) run /bin/sh (or whatever) to get the shell to do the redirection.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Neither of those worked. Didn't even get the blank email. KSH is the shell in use. – Malfist Jun 28 '12 at 19:15
  • @Malfist: Well I expect both of them will work if done correctly. Please edit your post with an example of how you tried using the shell. – Jon Skeet Jun 28 '12 at 19:17
  • @Malfist: Well for a start, you're using `available()` and assuming the whole file is going to be read in one go - always a bad idea. For the ksh example, you may well need to pass in `-c` to say that the rest of it is the command. – Jon Skeet Jun 28 '12 at 20:30
  • what would cause available not to return the whole file? They files are typically less than 5 megabytes so it's safe to read the whole thing into memory. – Malfist Jun 29 '12 at 01:59
  • @Malfist: It's safe to read the whole thing into memory, but you should *never* assume that a single call to `read()` will read the whole stream, or that `available()` will tell you the total length of the stream. (I'd make an exception for `ByteArrayInputStream`, but avoid `available()` there anyway.) Imagine a file is on a network file share, for example - I can well imagine it not returning everything in one go then. It's simply bad practice to read a file this way - there are perfectly reliable ways of doing it by looping. – Jon Skeet Jun 29 '12 at 05:44
2

Try exec'ing { "ksh", "-c", "mail me@example.com < " + etc }. The -c option tells the shell specifically to parse the next argument as a shell command with possible redirection and so on. Without the -c, ksh follows a heuristic to decide what to do with its command line, and it may not be running the command in the way you want it to.

Kenster
  • 23,465
  • 21
  • 80
  • 106
1

Split into two lines, just to get better readability:

        String cmd = "mail me@example.com < " + tempEmailFile.getAbsolutePath () ;
        Process p = Runtime.getRuntime().exec (cmd);

This will look for a program named "mail me@example.com < " + tempEmailFile.getAbsolutePath (). It will not do redirection - for that to do you have to read the output of that process yourself.

Furtermore it will not lookup the path, so you might have to specify the whole path /usr/bin/mail or whatever it is.

And you have to split command and parameters; use an Array of String instead: ("/path/to/prg", "param1", "param2", "foo=bar");

You can use redirection, if you call as program a script, like

String cmd = "/usr/bin/mail me@example.com < " + tempEmailFile.getAbsolutePath () ;
String cmdarr = new String [] {"/bin/bash", "-c", cmd}; 
Process p = Runtime.getRuntime().exec (cmdarr);

It is shorter than invoking file redirection from Java yourself, more simple but you lose the ability to react sensible on different errors.

user unknown
  • 35,537
  • 11
  • 75
  • 121
  • HP-UX uses ksh, will I need to add additional parameters? – Malfist Jun 28 '12 at 19:01
  • @Malfist: "didn't work" is a bit vague. I forgot to add an absolute path to mail again. Updating... - might need "-c" to pass a command as String, at least for bash. Consult the ksh-manual how to invoke the ksh binary with a script as string. – user unknown Jun 28 '12 at 20:00