1 /*
   2  * Copyright (c) 2003, 2020, 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     int length = sizeof(addr.sun_path);
 237     unlink(nameUtf);
 238     memset(&addr, 0, sizeof(addr));
 239     addr.sun_family = AF_UNIX;
 240     strncpy(addr.sun_path, nameUtf, length);
 241     addr.sun_path[length - 1] = '\0';
 242     ret = bind(sock, (const struct sockaddr*)&addr, sizeof(addr));
 243     if (ret == -1) {
 244         ThrowException(env, "java/io/IOException", "socket bind error");
 245     }
 246     ret = listen(sock, 5);
 247     if (ret == -1) {
 248         ThrowException(env, "java/io/IOException", "socket bind error");
 249     }
 250     (*env)->ReleaseStringUTFChars(env, name, nameUtf);
 251 }
 252 
 253 JNIEXPORT jint JNICALL Java_UnixDomainSocket_accept0
 254   (JNIEnv *env, jclass cls, jint sock)
 255 {
 256     struct sockaddr_storage addr;
 257     socklen_t len = sizeof(addr);
 258     int ret = accept(sock, (struct sockaddr *)&addr, &len);
 259     if (ret == -1)
 260         ThrowException(env, "java/io/IOException", "socket accept error");
 261     return ret;
 262 }
 263 
 264 JNIEXPORT void JNICALL Java_UnixDomainSocket_connect0
 265   (JNIEnv *env, jclass cls, jint fd, jstring name)
 266 {
 267     struct sockaddr_un addr;
 268     const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
 269     int ret = -1;
 270     int length = sizeof(addr.sun_path);
 271     memset(&addr, 0, sizeof(addr));
 272     addr.sun_family = AF_UNIX;
 273     strncpy(addr.sun_path, nameUtf, length);
 274     addr.sun_path[length - 1] = '\0';
 275     ret = connect(fd, (const struct sockaddr*)&addr, sizeof(addr));
 276     if (ret == -1) {
 277         ThrowException(env, "java/io/IOException", "socket connect error");
 278     }
 279     (*env)->ReleaseStringUTFChars(env, name, nameUtf);
 280 }
 281 
 282 
 283 JNIEXPORT jint JNICALL Java_UnixDomainSocket_read0
 284   (JNIEnv *env, jclass cls, jint fd)
 285 {
 286     int ret;
 287     unsigned char res;
 288     ret = read(fd, &res, 1);
 289     if (ret == 0)
 290         return -1; /* EOF */
 291     else if (ret < 0) {
 292         ThrowException(env, "java/io/IOException", "read error");
 293         return -1;
 294     }
 295     return res;
 296 }
 297 
 298 JNIEXPORT void JNICALL Java_UnixDomainSocket_write0
 299   (JNIEnv *env, jclass cls, jint fd, jint byte)
 300 {
 301     int ret;
 302     unsigned char w = (unsigned char)byte;
 303     ret = write(fd, &w, 1);
 304     if (ret < 0) {
 305         ThrowException(env, "java/io/IOException", "write error");
 306     }
 307 }
 308 
 309 JNIEXPORT void JNICALL Java_UnixDomainSocket_close0
 310   (JNIEnv *env, jclass cls, jint fd, jstring name)
 311 {
 312     close(fd);
 313     if (name != NULL) {
 314         const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
 315         unlink(nameUtf);
 316         (*env)->ReleaseStringUTFChars(env, name, nameUtf);
 317     }
 318 }