C code works with rubyinline, doesn't with C extension

So I’ve blatantly stolen some code from a perl module to do simple
kerberos authentication and not being the most adept C programmer I
have something I can’t figure out.

The following code using rubyinline works fine. The code below that
which is just a ruby c extension segfaults on the call to
krb5_free_cred_contents(ctx, &creds). If I comment it out then it
works. This is on Freebsd 6.1 using MIT kerberos, ruby 1.8.5 with the
latest RubyInline.


require ‘rubygems’
require ‘inline’

class Krb
inline(:C) do |builder|
builder.include ‘<krb5.h>’
builder.include ‘<stdio.h>’
builder.include ‘<strings.h>’
builder.add_compile_flags “-I/usr/local/include -L/usr/local/lib
-lkrb5 -lk5crypto -lcom_err”
builder.c "
int _krb5_auth(char* user, char* pass)
{
int krbret;
krb5_context ctx;
krb5_creds creds;
krb5_principal princ;

int ret = 0;

/* Initialize krb5 context...
*/
if ((krbret = krb5_init_context(&ctx))) {
        return krbret;
}

memset(&creds, 0, sizeof(krb5_creds));

/* Get principal name...
*/
if ((krbret = krb5_parse_name(ctx, user, &princ))) {
        ret = krbret;
        krb5_free_context(ctx);
}

/* Check the user's pasword...
*/
if ((krbret = krb5_get_init_creds_password(
  ctx, &creds, princ, pass, 0, NULL, 0, NULL, NULL))) {
        ret = krbret;
        krb5_free_cred_contents(ctx, &creds);
        krb5_free_principal(ctx, princ);
}

return ret;

}"
end
end

k = Krb.new
res = k._krb5_auth(‘test’,‘test’)
p res


#include “ruby.h”
#include <krb5.h>
#include <stdio.h>
#include <strings.h>

static VALUE _krb5_auth(VALUE self, VALUE _user, VALUE _pass) {
char * user = STR2CSTR(_user);
char * pass = STR2CSTR(_pass);

int                     krbret;
krb5_context                ctx;
krb5_creds                  creds;
krb5_principal              princ;

int ret = 0;
if ((krbret = krb5_init_context(&ctx))) {
        return INT2FIX(krbret);
}

memset(&creds, 0, sizeof(krb5_creds));
if ((krbret = krb5_parse_name(ctx, user, &princ))) {
        ret = krbret;
        krb5_free_context(ctx);
}
if ((krbret = krb5_get_init_creds_password(
  ctx, &creds, princ, pass, 0, NULL, 0, NULL, NULL))) {
        ret = krbret;
        /* krb5_free_cred_contents(ctx, &creds); Uncomment this to

get a segfault*/
krb5_free_principal(ctx, princ);
}

return INT2FIX(ret);

}

void Init_krb() {
VALUE Krb = rb_define_class(“Krb”, rb_cObject);
rb_define_method(Krb, “krb5_auth”, _krb5_auth,2);
}


require ‘mkmf’
extension_name = ‘krb’
dir_config("/usr/local/lib","/usr/local/include")
have_library(“krb5”,“krb5_init_context”)
have_library(“k5crypto”,“krb5_encrypt”)
have_library(“com_err”,“com_err”)
create_makefile(extension_name)

I forgot to mention. The c extension only fails when I pass bad
credentials and the call to krb5_get_init_creds_password fails. Also,
the extension code is the same as the C code generated by RubyInline
except for the Init_ definition.

Chris

On 9/18/06, Squeamizh [email protected] wrote:

the same bug, but for whatever reason, it is not manifesting itself.
That’s actually what was happening. The code should have done
something bad because I was calling krb5_free_cred_contents when no
credentials existed. Now why it didn’t segfault with RubyInline I
have no idea.

snacktime wrote:

I forgot to mention. The c extension only fails when I pass bad
credentials and the call to krb5_get_init_creds_password fails. Also,
the extension code is the same as the C code generated by RubyInline
except for the Init_ definition.

Unfortunately, it is difficult to say without knowing mroe about krb5’s
interface. Also, note that just because commenting a particular line
seems to fix the problem does not necessarilly mean that the problem is
caused by that line. By the same token, your inline version could have
the same bug, but for whatever reason, it is not manifesting itself.

That being said, I did see one thing that looks a little suspiscious.
When name parsing fails, it looks like everything gets cleaned up, but
then the function continues on to password parsing. If both name
parsing and password parsing fail, then everything gets cleaned up
twice.

If this is indeed the problem, then one solution is to return before
the name parsing if-block closes. You could add the following line at
the end of the name parsing if-block:

return INT2FIX(ret);