1 /*
   2  * Copyright (c) 2003, 2019, 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 <string.h>
  30 #include <sys/types.h>
  31 #include <sys/socket.h>
  32 #include <sys/un.h>
  33 #include <unistd.h>
  34 #include <dirent.h>
  35 #include <sys/stat.h>
  36 #include <fcntl.h>
  37 #include <ctype.h>
  38 
  39 #include "jni.h"
  40 
  41 #define CHECK(X) if ((X) == 0) {printf("JNI init error line %d\n", __LINE__); _exit(1);}
  42 
  43 static jclass unixSocketClass;
  44 static jmethodID unixSocketCtor;
  45 
  46 /*
  47  * Throws the exception of the given class name and detail message
  48  */
  49 static void ThrowException(JNIEnv *env, const char *name, const char *msg) {
  50     jclass cls = (*env)->FindClass(env, name);
  51     if (cls != NULL) {
  52         (*env)->ThrowNew(env, cls, msg);
  53     }
  54 }
  55 
  56 /*
  57  * Convert a jstring to an ISO 8859_1 encoded C string
  58  */
  59 static char* getString8859_1Chars(JNIEnv *env, jstring jstr) {
  60     int i;
  61     char *result;
  62     jint len = (*env)->GetStringLength(env, jstr);
  63     const jchar *str = (*env)->GetStringCritical(env, jstr, 0);
  64     if (str == 0) {
  65         return NULL;
  66     }
  67 
  68     result = (char*)malloc(len+1);
  69     if (result == 0) {
  70         (*env)->ReleaseStringCritical(env, jstr, str);
  71         ThrowException(env, "java/lang/OutOfMemoryError", NULL);
  72         return NULL;
  73     }
  74 
  75     for (i=0; i<len; i++) {
  76         jchar unicode = str[i];
  77         if (unicode <= 0x00ff)
  78             result[i] = unicode;
  79         else
  80             result[i] = '?';
  81     }
  82 
  83     result[len] = 0;
  84     (*env)->ReleaseStringCritical(env, jstr, str);
  85     return result;
  86 }
  87 
  88 
  89 /*
  90  * Class:     Launcher
  91  * Method:    launch0
  92  * Signature: ([Ljava/lang/String;I)V
  93  */
  94 JNIEXPORT void JNICALL Java_Launcher_launch0
  95   (JNIEnv *env, jclass cls, jobjectArray cmdarray, jint serviceFd)
  96 {
  97     pid_t pid;
  98     DIR* dp;
  99     struct dirent* dirp;
 100     int thisFd;
 101     char** cmdv;
 102     int i, cmdlen;
 103 
 104     /*
 105      * Argument 0 of the command array is the program name.
 106      * Here we just extract the program name and any arguments into
 107      * a command array suitable for use with execvp.
 108      */
 109     cmdlen = (*env)->GetArrayLength(env, cmdarray);
 110     if (cmdlen == 0) {
 111         ThrowException(env, "java/lang/IllegalArgumentException",
 112             "command array must at least include the program name");
 113         return;
 114     }
 115     cmdv = (char **)malloc((cmdlen + 1) * sizeof(char *));
 116     if (cmdv == NULL) {
 117         ThrowException(env, "java/lang/OutOfMemoryError", NULL);
 118         return;
 119     }
 120 
 121     for (i=0; i<cmdlen; i++) {
 122         jstring str = (*env)->GetObjectArrayElement(env, cmdarray, i);
 123         cmdv[i] = (char *) getString8859_1Chars(env, str);
 124         if (cmdv[i] == NULL) {
 125             return;
 126         }
 127     }
 128 
 129     /*
 130      * Command array must have NULL as the last entry
 131      */
 132     cmdv[cmdlen] = NULL;
 133 
 134     /*
 135      * Launch the program. As this isn't a complete inetd or Runtime.exec
 136      * implementation we don't have a reaper to pick up child exit status.
 137      */
 138 #ifdef __solaris__
 139     pid = fork1();
 140 #else
 141     pid = fork();
 142 #endif
 143     if (pid != 0) {
 144         if (pid < 0) {
 145             ThrowException(env, "java/io/IOException", "fork failed");
 146         }
 147         return;
 148     }
 149 
 150     /*
 151      * We need to close all file descriptors except for serviceFd. To
 152      * get the list of open file descriptos we read through /proc/self/fd (/dev/fd)
 153      * but to open this requires a file descriptor. We could use a specific
 154      * file descriptor and fdopendir but Linux doesn't seem to support
 155      * fdopendir. Instead we use opendir and make an assumption on the
 156      * file descriptor that is used (by opening & closing a file).
 157      */
 158     thisFd = open("/dev/fd", O_RDONLY);
 159     if (thisFd < 0) {
 160         _exit(-1);
 161     }
 162 
 163     if ((dp = fdopendir(thisFd)) == NULL) {
 164         _exit(-1);
 165     }
 166 
 167     while ((dirp = readdir(dp)) != NULL) {
 168         if (isdigit(dirp->d_name[0])) {
 169             int fd = strtol(dirp->d_name, NULL, 10);
 170             if (fd != serviceFd && fd != thisFd) {
 171                 close(fd);
 172             }
 173         }
 174     }
 175     closedir(dp);
 176 
 177     /*
 178      * At this point all file descriptors are closed except for
 179      * serviceFd. We not dup 0,1,2 to this file descriptor and
 180      * close serviceFd. This should leave us with only 0,1,2
 181      * open and all connected to the same socket.
 182      */
 183     dup2(serviceFd, STDIN_FILENO);
 184     dup2(serviceFd, STDOUT_FILENO);
 185     dup2(serviceFd, STDERR_FILENO);
 186     close(serviceFd);
 187 
 188     execvp(cmdv[0], cmdv);
 189     _exit(-1);
 190 }
 191 
 192 JNIEXPORT void JNICALL Java_UnixDomainSocket_init(JNIEnv *env, jclass cls) {
 193     CHECK(unixSocketClass = (*env)->FindClass(env, "UnixDomainSocket"));
 194     CHECK(unixSocketClass = (*env)->NewGlobalRef(env, unixSocketClass));
 195     CHECK(unixSocketCtor = (*env)->GetMethodID(env, unixSocketClass, "<init>", "(I)V"));
 196 }
 197 
 198 /*
 199  * Class:     UnixDomainSocket
 200  * Method:    socketpair
 201  * Signature: ()[LUnixDomainSocket
 202  */
 203 JNIEXPORT jobjectArray JNICALL Java_UnixDomainSocket_socketpair
 204   (JNIEnv *env, jclass cls)
 205 {
 206     int fds[2];
 207     jobject socket;
 208     jobjectArray result = (*env)->NewObjectArray(env, 2, unixSocketClass, 0);
 209     if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
 210         perror("socketpair");
 211         return result;
 212     }
 213     socket = (*env)->NewObject(env, unixSocketClass, unixSocketCtor, fds[0]);
 214     (*env)->SetObjectArrayElement(env, result, 0, socket);
 215     socket = (*env)->NewObject(env, unixSocketClass, unixSocketCtor, fds[1]);
 216     (*env)->SetObjectArrayElement(env, result, 1, socket);
 217     return result;
 218 }
 219 
 220 JNIEXPORT jint JNICALL Java_UnixDomainSocket_create
 221   (JNIEnv *env, jclass cls)
 222 {
 223     int sock = socket(AF_UNIX, SOCK_STREAM, 0);
 224     if (sock == -1) {
 225         ThrowException(env, "java/io/IOException", "socket create error");
 226     }
 227     return sock;
 228 }
 229 
 230 JNIEXPORT void JNICALL Java_UnixDomainSocket_bind0
 231   (JNIEnv *env, jclass cls, jint sock, jstring name)
 232 {
 233     struct sockaddr_un addr;
 234     const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
 235     int ret = -1;
 236     unlink(nameUtf);
 237     memset(&addr, 0, sizeof(addr));
 238     addr.sun_family = AF_UNIX;
 239     strncpy(addr.sun_path, nameUtf, strlen(nameUtf));
 240     ret = bind(sock, (const struct sockaddr*)&addr, sizeof(addr));
 241     if (ret == -1) {
 242         ThrowException(env, "java/io/IOException", "socket bind error");
 243     }
 244     ret = listen(sock, 5);
 245     if (ret == -1) {
 246         ThrowException(env, "java/io/IOException", "socket bind error");
 247     }
 248     (*env)->ReleaseStringUTFChars(env, name, nameUtf);
 249 }
 250 
 251 JNIEXPORT jint JNICALL Java_UnixDomainSocket_accept0
 252   (JNIEnv *env, jclass cls, jint sock)
 253 {
 254     struct sockaddr_storage addr;
 255     socklen_t len = sizeof(addr);
 256     int ret = accept(sock, (struct sockaddr *)&addr, &len);
 257     if (ret == -1)
 258         ThrowException(env, "java/io/IOException", "socket accept error");
 259     return ret;
 260 }
 261 
 262 JNIEXPORT void JNICALL Java_UnixDomainSocket_connect0
 263   (JNIEnv *env, jclass cls, jint fd, jstring name)
 264 {
 265     struct sockaddr_un addr;
 266     const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
 267     int ret = -1;
 268     memset(&addr, 0, sizeof(addr));
 269     addr.sun_family = AF_UNIX;
 270     strncpy(addr.sun_path, nameUtf, strlen(nameUtf));
 271     ret = connect(fd, (const struct sockaddr*)&addr, sizeof(addr));
 272     if (ret == -1) {
 273         ThrowException(env, "java/io/IOException", "socket connect error");
 274     }
 275     (*env)->ReleaseStringUTFChars(env, name, nameUtf);
 276 }
 277 
 278 
 279 JNIEXPORT jint JNICALL Java_UnixDomainSocket_read0
 280   (JNIEnv *env, jclass cls, jint fd)
 281 {
 282     int ret;
 283     unsigned char res;
 284     ret = read(fd, &res, 1);
 285     if (ret == 0)
 286         return -1; /* EOF */
 287     else if (ret < 0) {
 288         ThrowException(env, "java/io/IOException", "read error");
 289         return -1;
 290     }
 291     return res;
 292 }
 293 
 294 JNIEXPORT void JNICALL Java_UnixDomainSocket_write0
 295   (JNIEnv *env, jclass cls, jint fd, jint byte)
 296 {
 297     int ret;
 298     unsigned char w = (unsigned char)byte;
 299     ret = write(fd, &w, 1);
 300     if (ret < 0) {
 301         ThrowException(env, "java/io/IOException", "write error");
 302     }
 303 }
 304 
 305 JNIEXPORT void JNICALL Java_UnixDomainSocket_close0
 306   (JNIEnv *env, jclass cls, jint fd, jstring name)
 307 {
 308     close(fd);
 309     if (name != NULL) {
 310         const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
 311         unlink(nameUtf);
 312         (*env)->ReleaseStringUTFChars(env, name, nameUtf);
 313     }
 314 }