1 /*
   2  * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * A simple launcher to launch a program as if it was launched by inetd.
  26  */
  27 #include <stdio.h>
  28 #include <stdlib.h>
  29 #include <sys/types.h>
  30 #include <sys/socket.h>
  31 #include <unistd.h>
  32 #include <dirent.h>
  33 #include <sys/stat.h>
  34 #include <fcntl.h>
  35 #include <ctype.h>
  36 
  37 #include "jni.h"
  38 
  39 /*
  40  * Throws the exception of the given class name and detail message
  41  */
  42 static void ThrowException(JNIEnv *env, const char *name, const char *msg) {
  43     jclass cls = (*env)->FindClass(env, name);
  44     if (cls != NULL) {
  45         (*env)->ThrowNew(env, cls, msg);
  46     }
  47 }
  48 
  49 /*
  50  * Convert a jstring to an ISO 8859_1 encoded C string
  51  */
  52 static char* getString8859_1Chars(JNIEnv *env, jstring jstr) {
  53     int i;
  54     char *result;
  55     jint len = (*env)->GetStringLength(env, jstr);
  56     const jchar *str = (*env)->GetStringCritical(env, jstr, 0);
  57     if (str == 0) {
  58         return NULL;
  59     }
  60 
  61     result = (char*)malloc(len+1);
  62     if (result == 0) {
  63         (*env)->ReleaseStringCritical(env, jstr, str);
  64         ThrowException(env, "java/lang/OutOfMemoryError", NULL);
  65         return NULL;
  66     }
  67 
  68     for (i=0; i<len; i++) {
  69         jchar unicode = str[i];
  70         if (unicode <= 0x00ff)
  71             result[i] = unicode;
  72         else
  73             result[i] = '?';
  74     }
  75 
  76     result[len] = 0;
  77     (*env)->ReleaseStringCritical(env, jstr, str);
  78     return result;
  79 }
  80 
  81 
  82 /*
  83  * Class:     Launcher
  84  * Method:    launch0
  85  * Signature: ([Ljava/lang/String;I)V
  86  */
  87 JNIEXPORT void JNICALL Java_Launcher_launch0
  88   (JNIEnv *env, jclass cls, jobjectArray cmdarray, jint serviceFd)
  89 {
  90     pid_t pid;
  91     DIR* dp;
  92     struct dirent* dirp;
  93     int thisFd;
  94     char** cmdv;
  95     int i, cmdlen;
  96 
  97     /*
  98      * Argument 0 of the command array is the program name.
  99      * Here we just extract the program name and any arguments into
 100      * a command array suitable for use with execvp.
 101      */
 102     cmdlen = (*env)->GetArrayLength(env, cmdarray);
 103     if (cmdlen == 0) {
 104         ThrowException(env, "java/lang/IllegalArgumentException",
 105             "command array must at least include the program name");
 106         return;
 107     }
 108     cmdv = (char **)malloc((cmdlen + 1) * sizeof(char *));
 109     if (cmdv == NULL) {
 110         ThrowException(env, "java/lang/OutOfMemoryError", NULL);
 111         return;
 112     }
 113 
 114     for (i=0; i<cmdlen; i++) {
 115         jstring str = (*env)->GetObjectArrayElement(env, cmdarray, i);
 116         cmdv[i] = (char *) getString8859_1Chars(env, str);
 117         if (cmdv[i] == NULL) {
 118             return;
 119         }
 120     }
 121 
 122     /*
 123      * Command array must have NULL as the last entry
 124      */
 125     cmdv[cmdlen] = NULL;
 126 
 127     /*
 128      * Launch the program. As this isn't a complete inetd or Runtime.exec
 129      * implementation we don't have a reaper to pick up child exit status.
 130      */
 131 #ifdef __solaris__
 132     pid = fork1();
 133 #else
 134     pid = fork();
 135 #endif
 136     if (pid != 0) {
 137         if (pid < 0) {
 138             ThrowException(env, "java/io/IOException", "fork failed");
 139         }
 140         return;
 141     }
 142 
 143     /*
 144      * We need to close all file descriptors except for serviceFd. To
 145      * get the list of open file descriptos we read through /proc/self/fd
 146      * but to open this requires a file descriptor. We could use a specific
 147      * file descriptor and fdopendir but Linux doesn't seem to support
 148      * fdopendir. Instead we use opendir and make an assumption on the
 149      * file descriptor that is used (by opening & closing a file).
 150      */
 151     thisFd = open("/dev/null", O_RDONLY);
 152     if (thisFd < 0) {
 153         _exit(-1);
 154     }
 155     close(thisFd);
 156 
 157     if ((dp = opendir("/proc/self/fd")) == NULL) {
 158         _exit(-1);
 159     }
 160 
 161     while ((dirp = readdir(dp)) != NULL) {
 162         if (isdigit(dirp->d_name[0])) {
 163             int fd = strtol(dirp->d_name, NULL, 10);
 164             if (fd != serviceFd && fd != thisFd) {
 165                 close(fd);
 166             }
 167         }
 168     }
 169     closedir(dp);
 170 
 171     /*
 172      * At this point all file descriptors are closed except for
 173      * serviceFd. We not dup 0,1,2 to this file descriptor and
 174      * close serviceFd. This should leave us with only 0,1,2
 175      * open and all connected to the same socket.
 176      */
 177     dup2(serviceFd, STDIN_FILENO);
 178     dup2(serviceFd, STDOUT_FILENO);
 179     dup2(serviceFd, STDERR_FILENO);
 180     close(serviceFd);
 181 
 182     execvp(cmdv[0], cmdv);
 183     _exit(-1);
 184 }