Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1706935
  • 博文数量: 607
  • 博客积分: 10031
  • 博客等级: 上将
  • 技术积分: 6633
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-30 17:41
文章分类

全部博文(607)

文章存档

2011年(2)

2010年(15)

2009年(58)

2008年(172)

2007年(211)

2006年(149)

我的朋友

分类: C/C++

2006-06-23 13:45:36

==============================================================================

Interfacing C with Java in Linux - The JNI Solution

==============================================================================



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 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

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
#include "jniexample.h"
#include
#include // For strdup()
#include // For crypt()
#include // For getspent()
#include

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 . All Rights Reserved
阅读(751) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~