==============================================================================
C-Scene Issue #04
Interfacing C with Java in Linux - The JNI Solution
Dana M. Epp
==============================================================================



Oh no... not the J word


The hype. Java is the end all of distributed computing programming solutions. The reality. Although powerful in many aspects of networked computing, it still comes short when required to do low level work with the operating system. The solution. When push comes to shove, alittle ingenious C code can do the trick.

OK, I know once published this is going to start some flamefests. Lets get right to it. Send it to me at /dev/null and move on. If you are a Java advocate you may have some god aweful rationale in why there is not a need for low level programming. However, this article is designed to show how in some cases, the need outweights the Pure java ideology.

I am far from being an advocate of any language. However, I like the direction and movement Java is having, and I think that it has a lot of power now, and a lot of potential for the future. If developers would keep an open mind about such things, I wouldn't need to explain why I have a /dev/null mailbox.

Moving On

Lets set up a problem we can solve in this column, and get some real world experience rather than yet another "Hello World" application done. In our example, we are going to surround a specific platform need to a simple java application so we can get our task done. Lets define it here so we can get to some real code.. rather than my useless drival.

Defining the Problem

Problem:

We need to authenticate a user on the local linux system before he/she/it can log on to the application and begin taking over the world. Since the linux system uses shadow passwords, we must have a method to retrieve the password and authenticate the user in the user shadow database. Once authenticated, this user can work with the application.

Platform specifics

Now that we laid down the problem, lets define what platform we will work on and what tools are used. These are the specs for the architecture this application was done on. It is not made to impress you( if yer on an old 386), but to impress upon you how the development platform doesn't matter, but moreover, libraries to access the native operating system do. Machine: x86 P200 MMX, 80M ram with all the trimmings OS: Debian 2 on Linux kernel 2.0.33 Java: JDK 1.1.5v6 glibc Compiler: gcc 2.7.2.3 Editor: nvi

Oh waiter, another cup of java please!

JNI is Sun's solution on how to bridge native code into a java class. Although I could bore you with the internals of how this works, and what its use is, I will instead refer you to http://java.sun.com/docs/books/tutorial/native1.1/implimenting/index.html, where you can read about how it works in detail.

I will however summarize how it works so we can get down and dirty. Basically, you set up an entry point in which a Java class can call the library function, which in turn will execute the native code, and return back to the class.

OK, I know some of you already have a question. Being that Java and C type varibles are different, how the heck can you pass values across the library boundry. Well... thats a good question.

Basically, Sun mapped the Java types to its native counter parts. Here is a summary table of how it looks:

Typ NativeType Size in bits



boolean jboolean unsigned 8
byte jbyte 8
char jchar unsigned 16
short jshort 16
int jint 32
long jlong 64
float jfloat 32
double jdouble 64
void void n/a

If you want to know where I got this, check out the jni.h file in your include directory. In my case, it was located in /usr/local/jdk1.1.5/include/jni.h .

Now that you know the basic primitive types, you can use them across the boundary and know its represented properly.

Natively Speaking

In my latter paragraph, I stated that you need to set up an entry point to call the library function you want. To do so, you simply use the "native" keyword. In our example, we will create a method/function in the application called validUser, which takes two parameters, the username we are supposed to validate, and the password attempt. It will return a boolean value, telling us if the user authenticated or not. It may look something like this:

public static native boolean validUser( String strUser, String strPass );

Simple Application

Ok, lets write a simple java application that will use this. In this example, we simply will prompt for a username and password, authenticate it, and then echo out if the password was right or not. In our case, since the shadow password file in Linux is only accessable to root, this program would need to be executed as root. This isn't even close to being a secure program, since it doesn't mask echo the password, and you can break out of the program. However, since the scope of this program is to teach about JNI, and not write the next killer security logon sequence... we'll live. // ************************************************************************ // // jniexample.java // // Simple jni example showing how to bridge C style code into a java // class. // // Author: Dana M. Epp // Nick: SilverStr // // Copyright (c) 1998 NetMaster Networking Solutions, Inc. // All Rights Reserved. // // Released through GPL for C-Scene article. // // NNS and its employees are not liable if you stick your tounge on frozen // monkey bars, go postal on the Redmond campus or get hit be a Mac truck. // Therefore, NNS and their employees are also not liable for any use, misuse // idiocy, or othwerwise damanging results from this code. Oh ya... we are not // responsible for BSOD when starting notepad. Use at your own risk. // // ************************************************************************ import java.io.*; import java.util.*; class jniexample { // Try to load the native Code library for user Authentication. static { try { System.loadLibrary( "validuser" ); } catch( UnsatisfiedLinkError e ) { System.out.println( "Could not load native code for user authentication." ); System.exit(1); } } public static void main( String [] args ) throws IOException { String strUser; String strPass; // Set up a console to read user input data BufferedReader console = new BufferedReader( new InputStreamReader( System.in ) ); System.out.println( "*JNI Example* by Dana M. Epp (SilverStr)\n" ); // Loop forever until a valid password for( ;; ) { // Read in username System.out.print( "Username: " ); strUser = console.readLine(); // Read in password attempt System.out.print( "Password: " ); strPass = console.readLine(); if( validUser( strUser, strPass ) ) { // User validates... exit loop break; } else { System.out.println( "\nInvalid Logon attempt.\n" ); } } System.out.println( "Hello " + strUser + ", your password has been verified." ); } // This is the Entry point. public static native boolean validUser( String strUser, String strPass ); } // ********************** End Code ******************************************

Whew... thats out of the way

Ok, if you are new to java.. you may want to look up some of that. However, basically it is prompting for a username and password, and then calling the entry point validUser() to verify if the user is valid. Notice how the native function is at the end, and has no code.... it will be provided in the C function. Also notice that I statically load the library into the class. Umm.. where's the C function ?

Well, before we get to the function, we need to do a few things. First off, we need to compile the java into bytecode and extract some C style headers. Compile as follows: javac jniexample.java This will give you jniexample.class . If you don't understand how the byte code is generated, please RTFM at www.javasoft.com. Now that we have the bytecode class ready to go, we need to generate a JNI style header we can use with our C code. To do so we need to use the javah tool as follows: javah -jni jniexample You will now notice that residing in the same directory as the byte code, a new file called jniexample.h exists. Lets examine it for a second and see what happened. Ugly as sin... but yet so elegant Need I say more? JNIEXPORT jboolean JNICALL Java_jniexample_validUser (JNIEnv *, jclass, jstring, jstring); That, my friends, is the prototype declaration for our C function. The native method function definition in the code must match the generated function prototype in the header file. If it doesn't, it WILL NOT WORK! I can't tell you how many times I answer this in #java when people start yelling at the cruel world about their JNI not working. Always include JNIEXPORT and JNICALL in your native method function prototypes. JNIEXPORT and JNICALL ensure that the source code compiles on platforms such as Win32 that require special keywords for functions exported from dynamic link libraries. Header in place, lets build this beast! With the header in place, lets build this beast. Below is jniexample.c, the main C source code file used as the native function. /************************************************************************** jniexample.c Our native function. See jniexample.java for compyright information. Author: Dana M. Epp (aka SilverStr) **************************************************************************/ #include <jni.h> #include "jniexample.h" #include <stdio.h> #include <string.h> // For strdup() #include <unistd.h> // For crypt() #include <shadow.h> // For getspent() #include <sys/types.h> JNIEXPORT jboolean JNICALL Java_jniexample_validUser ( JNIEnv *env, jobject obj, jstring user, jstring pass ) { struct spwd *pw; char salt[2]; char *cpass; jboolean retVal= JNI_FALSE; /* Convert to a usable C string */ const char *pUser = (*env)->GetStringUTFChars( env, user, 0 ); const char *pPass = (*env)->GetStringUTFChars( env, pass, 0 ); /* Initialize */ pw = getspent(); /* Run though the passwd file and find user */ while( strcmp( pw->sp_namp, pUser ) != 0 ) { pw = getspent(); if( pw == NULL ) break; } if( pw != NULL ) { /* Salt is first two chars of unix passwd */ salt[0] = pw->sp_pwdp[0]; salt[1] = pw->sp_pwdp[1]; /* Crypt the password given */ cpass = (char*)crypt( pPass, salt ); /* Compare */ if ( strcmp( pw->sp_pwdp, cpass ) == 0 ) { retVal = JNI_TRUE; } } /* Clean up after ourselves. */ endspent(); /* Free as a bird.... */ (*env)->ReleaseStringUTFChars( env, user, pUser ); (*env)->ReleaseStringUTFChars( env, pass, pPass ); return retVal; } /* End of Code */ OK.. before you freak out wondering what half that was.... lets compile it. Here is how I compiled it, and what I used for command line parameters: gcc -o libvaliduser.so jniexample.c -shared -fpic -lcrypt -I/usr/local/jdk1.1.5/include -I/usr/local/jdk1.1.5/include/genunix Some parameters to note in the compile are noted below. -fpic If supported for the target machines, generate position-independent code, suitable for use in a shared library. -shared Produce a shared object which can then be linked with other objects to form an executable. Only a few systems support this option. Guess what... linux is one of them. -lcrypt Library to include the crypt functions. You should also note that I have forced include paths for the JNI stuff, and, since I am on a unix, I ensure I get the unix additional header information in genunix. Your milage may vary on this approach, but this works best for me on glibc. In comparision, I just built this on a Debian 1.3.1 machine... and there was not a need for the -lcrypt. Go figure. Pass the Tylonol... lets explain what we just coded I bet you are wondering just what the heck some of those commands were in that C function. Some of you may be wondering just how that grabbed the password. Even more may wonder where the white goes when snow melts. In any case.. lets deal with the questions one by one. The first interesting command you may note is in this line: jboolean retVal= JNI_FALSE; This is simply a return value we will use to pass back a boolean value to the java class. Note we use JNI_FALSE... which is defined in jni.h . You can review the chart in a latter section of this article if you already forgot. The next line that looks kewl is: const char *pUser = (*env)->GetStringUTFChars( env, user, 0 ); This is a "meat and potatos" call. It is one of the workhorses that handles varibles between java and C. In this case, it will convert a java String varible into a character pointer in C. To do so requires access to the enviroment passing the params ( JNIEnv *env ) and calling the GetStringUTFChars function. Matching the allocation created by the GetStringUTFChars call, is the line: (*env)->ReleaseStringUTFChars( env, user, pUser ); This cleans up the allocated memory for you, and then allows you to exit the library cleanly. Now lets move on to so internal C functions. Although this doesn't really surround the JNI tutorial here, its more of interest to those wanting to know how I authenticated the password. If you are not interested in this, and are more interested in the JNI, just move on to the next topic and skip this part. Still with me? Good. Hard-core coder. Lets move on. As most of you may know, unix has a passwd file, usually located in /etc/passwd. This file stores the users profile on the system, as well as the encrypted password, unless you use shadow passwords. The world moved to shadow passwords when it found that most people could easily read the passwd file and run a brute hack on the password. (There are other reasons, but this is a key one.). Moving the password into a "shadow" file which is only readable by root allows the system to have another buffer zone between the user and the password. It also forces us to use a different routine to retrieve passwords than the getpwent() that most of us are used to. Thanking the lucky stars, someone made a command just as easy called getspent. Ya ya.. sounds like something Bart Simpson would say. However, its a good function to know. It will get a shadow password entry and store it in a spwd(shadow password) struct. If you simply walk through the shadow database user by user comparing the username, you can then use that information as a key to "salt" password for a crypt and compare. That is exactly what that while loop does. It walks through.. and then breaks when either it finds the user, or no user exists. Once the entry is available, we can then salt the first two characters of the unix password. This is a standard procedure for the password system, as it results in a key for an encrypted password. >From that point, it then crypts the users password, based on the two character salt ripped from the spent struct. Now, you can compare the two passwords, and see if they match. If they do... you got a winner.. if not.. you got a whiner. Thats all there is to it. Wheeeeeeeeeew. Oh by the way... if you wanna know the answer to the white/snow question, come ask me about the refraction of light on #c or #java. Shhhh... the library is a place of quiet tranquaility... WAKE UP! Now that I got your attention, listen closely. Are you listening? Good... because what I am about to say I end up explaining atleast once a week in either #java or #c. You MUST ensure that the library is accessable to the java class. On a unix environment.. this means you must place it in a library path!! In my case... I copied it to /usr/local/lib, and then ran ldconfig. You could also manually edit ld.so.conf and add a new lib path... but you are then spreading out your libraries. Choice is up to you on that one. In whichever way you go.. make SURE you have the library accessable. Here is what I did: cp libvaliduser.so /usr/local/lib && ldconfig Now what? Run the program turkey! You will find, if you sacrifice your first born to the Penguin gods, chant the hackers bible backwards, vow that VB is a middle management attempt to be a 'leet h4ck0r, and then follow directions.. you will be prompted for a username and password. If it fails... you are asked again. If it succeeds, it tells you so... and drops out. What more can I say.. you just interfaced a C function to java. Now that you actually did it.. go pray to the mightly penguin gods. To hear them reply back... just: `cat /proc/kcore > /dev/audio.` See ya on the net!


This page is Copyright © 1997 By C Scene. All Rights Reserved