472 lines
13 KiB
C
472 lines
13 KiB
C
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
#include <sasl/sasl.h>
|
|
#include <ruby.h>
|
|
|
|
static VALUE mSasl;
|
|
|
|
#define INPUT_SIZE 512
|
|
#define MECH_SIZE 32
|
|
|
|
typedef void* sasl_context_t;
|
|
|
|
#define QSASL_OK 0
|
|
#define QSASL_CONTINUE 1
|
|
#define QSASL_FAILED 2
|
|
|
|
typedef struct {
|
|
char magic[8];
|
|
sasl_conn_t* conn;
|
|
sasl_callback_t callbacks[8];
|
|
char* userName;
|
|
char* password;
|
|
char* operUserName;
|
|
unsigned int minSsf;
|
|
unsigned int maxSsf;
|
|
char mechanism[MECH_SIZE];
|
|
char input[INPUT_SIZE];
|
|
} context_t;
|
|
|
|
//
|
|
// Resolve forward references
|
|
//
|
|
static VALUE qsasl_free(int, VALUE*, VALUE);
|
|
|
|
//
|
|
// Validate an input string to ensure that it is either NULL or of reasonable size.
|
|
//
|
|
static int qsasl_valid(char* str)
|
|
{
|
|
int idx;
|
|
|
|
if (str == 0)
|
|
return 1;
|
|
|
|
for (idx = 0; idx < INPUT_SIZE; idx++) {
|
|
if (str[idx] == '\0')
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// SASL callback for identity and authentication identity.
|
|
//
|
|
static int qsasl_cb_user(void* _context, int id, const char **result, unsigned *len)
|
|
{
|
|
context_t* context = (context_t*) _context;
|
|
|
|
if (context->userName)
|
|
*result = context->userName;
|
|
|
|
return SASL_OK;
|
|
}
|
|
|
|
//
|
|
// SASL callback for passwords.
|
|
//
|
|
static int qsasl_cb_password(sasl_conn_t* conn, void* _context, int id, sasl_secret_t **psecret)
|
|
{
|
|
context_t* context = (context_t*) _context;
|
|
sasl_secret_t* secret;
|
|
size_t length;
|
|
|
|
if (context->password)
|
|
length = strlen(context->password);
|
|
else
|
|
length = 0;
|
|
|
|
secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length);
|
|
secret->len = length;
|
|
if (length)
|
|
memcpy(secret->data, context->password, length);
|
|
*psecret = secret;
|
|
|
|
return SASL_OK;
|
|
}
|
|
|
|
//
|
|
// Interactively prompt the user for authentication data.
|
|
//
|
|
static void qsasl_prompt(sasl_context_t _context, sasl_interact_t* interact)
|
|
{
|
|
context_t* context = (context_t*) _context;
|
|
char *pass;
|
|
char *input;
|
|
char passwdPrompt[100];
|
|
|
|
if (interact->id == SASL_CB_PASS) {
|
|
strncpy(passwdPrompt, interact->prompt, 95);
|
|
strcat(passwdPrompt, ": ");
|
|
pass = getpass(passwdPrompt);
|
|
strncpy(context->input, pass, INPUT_SIZE - 1);
|
|
context->input[INPUT_SIZE - 1] = '\0';
|
|
} else {
|
|
printf(interact->prompt);
|
|
if (interact->defresult) {
|
|
printf(" (%s)", interact->defresult);
|
|
}
|
|
printf(": ");
|
|
input = fgets(context->input, INPUT_SIZE, stdin);
|
|
if (input != context->input) {
|
|
rb_raise(rb_eRuntimeError, "Unexpected EOF on interactive prompt");
|
|
}
|
|
}
|
|
|
|
interact->result = context->input;
|
|
interact->len = strlen(context->input);
|
|
}
|
|
|
|
//
|
|
// Initialize the SASL client library.
|
|
//
|
|
static VALUE qsasl_client_init()
|
|
{
|
|
int result;
|
|
|
|
result = sasl_client_init(0);
|
|
if (result != SASL_OK)
|
|
rb_raise(rb_eRuntimeError,
|
|
"sasl_client_init failed: %d - %s",
|
|
result, sasl_errstring(result, -0, 0));
|
|
return Qnil;
|
|
}
|
|
|
|
//
|
|
// Allocate a new SASL client context.
|
|
//
|
|
static VALUE qsasl_client_new(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
char* mechanism = 0;
|
|
char* serviceName = 0;
|
|
char* hostName = 0;
|
|
char* userName = 0;
|
|
char* password = 0;
|
|
unsigned int minSsf = 0;
|
|
unsigned int maxSsf = 65535;
|
|
|
|
int result;
|
|
int i = 0;
|
|
context_t *context;
|
|
sasl_security_properties_t secprops;
|
|
|
|
if (argc != 7)
|
|
rb_raise(rb_eRuntimeError, "Wrong number of arguments");
|
|
|
|
if (!NIL_P(argv[0]))
|
|
mechanism = StringValuePtr(argv[0]);
|
|
if (!NIL_P(argv[1]))
|
|
serviceName = StringValuePtr(argv[1]);
|
|
if (!NIL_P(argv[2]))
|
|
hostName = StringValuePtr(argv[2]);
|
|
if (!NIL_P(argv[3]))
|
|
userName = StringValuePtr(argv[3]);
|
|
if (!NIL_P(argv[4]))
|
|
password = StringValuePtr(argv[4]);
|
|
minSsf = FIX2INT(argv[5]);
|
|
maxSsf = FIX2INT(argv[6]);
|
|
|
|
if (!qsasl_valid(mechanism) || !qsasl_valid(serviceName) ||
|
|
!qsasl_valid(hostName) || !qsasl_valid(userName) ||
|
|
!qsasl_valid(password)) {
|
|
rb_raise(rb_eRuntimeError, "Invalid string argument");
|
|
}
|
|
|
|
context = (context_t*) malloc(sizeof(context_t));
|
|
memset(context, 0, sizeof(context_t));
|
|
strcpy(context->magic, "QSASL01");
|
|
|
|
context->minSsf = minSsf;
|
|
context->maxSsf = maxSsf;
|
|
if (mechanism != 0) {
|
|
strncpy(context->mechanism, mechanism, MECH_SIZE - 1);
|
|
context->mechanism[MECH_SIZE - 1] = '\0';
|
|
}
|
|
|
|
context->callbacks[i].id = SASL_CB_GETREALM;
|
|
context->callbacks[i].proc = 0;
|
|
context->callbacks[i++].context = 0;
|
|
|
|
if (userName != 0 && userName[0] != '\0') {
|
|
context->userName = (char*) malloc(strlen(userName) + 1);
|
|
strcpy(context->userName, userName);
|
|
|
|
context->callbacks[i].id = SASL_CB_USER;
|
|
context->callbacks[i].proc = qsasl_cb_user;
|
|
context->callbacks[i++].context = context;
|
|
|
|
context->callbacks[i].id = SASL_CB_AUTHNAME;
|
|
context->callbacks[i].proc = qsasl_cb_user;
|
|
context->callbacks[i++].context = context;
|
|
}
|
|
|
|
context->callbacks[i].id = SASL_CB_PASS;
|
|
if (password != 0 && password[0] != '\0') {
|
|
context->password = (char*) malloc(strlen(password) + 1);
|
|
strcpy(context->password, password);
|
|
|
|
context->callbacks[i].proc = qsasl_cb_password;
|
|
} else
|
|
context->callbacks[i].proc = 0;
|
|
context->callbacks[i++].context = context;
|
|
|
|
context->callbacks[i].id = SASL_CB_LIST_END;
|
|
context->callbacks[i].proc = 0;
|
|
context->callbacks[i++].context = 0;
|
|
|
|
result = sasl_client_new(serviceName, hostName, 0, 0,
|
|
context->callbacks, 0, &context->conn);
|
|
|
|
if (result != SASL_OK) {
|
|
context->conn = 0;
|
|
qsasl_free(1, (VALUE*) &context, Qnil);
|
|
rb_raise(rb_eRuntimeError, "sasl_client_new failed: %d - %s",
|
|
result, sasl_errstring(result, 0, 0));
|
|
}
|
|
|
|
secprops.min_ssf = minSsf;
|
|
secprops.max_ssf = maxSsf;
|
|
secprops.maxbufsize = 65535;
|
|
secprops.property_names = 0;
|
|
secprops.property_values = 0;
|
|
secprops.security_flags = 0;//TODO: provide means for application to configure these
|
|
|
|
result = sasl_setprop(context->conn, SASL_SEC_PROPS, &secprops);
|
|
if (result != SASL_OK) {
|
|
qsasl_free(1, (VALUE*) &context, Qnil);
|
|
rb_raise(rb_eRuntimeError, "sasl_setprop failed: %d - %s",
|
|
result, sasl_errdetail(context->conn));
|
|
}
|
|
|
|
return (VALUE) context;
|
|
}
|
|
|
|
//
|
|
// Free a SASL client context.
|
|
//
|
|
static VALUE qsasl_free(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
context_t* context;
|
|
|
|
if (argc == 1)
|
|
context = (context_t*) argv[0];
|
|
else
|
|
rb_raise(rb_eRuntimeError, "Wrong Number of Arguments");
|
|
|
|
if (context->conn)
|
|
sasl_dispose(&context->conn);
|
|
if (context->userName)
|
|
free(context->userName);
|
|
if (context->password)
|
|
free(context->password);
|
|
if (context->operUserName)
|
|
free(context->operUserName);
|
|
free(context);
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
//
|
|
// Start the SASL exchange from the client's point of view.
|
|
//
|
|
static VALUE qsasl_client_start(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
context_t* context;
|
|
char* mechList;
|
|
char* mechToUse;
|
|
int result;
|
|
int propResult;
|
|
const char* response;
|
|
unsigned int len;
|
|
sasl_interact_t* interact = 0;
|
|
const char* chosen;
|
|
const char* operName;
|
|
|
|
if (argc == 2) {
|
|
context = (context_t*) argv[0];
|
|
mechList = StringValuePtr(argv[1]);
|
|
} else
|
|
rb_raise(rb_eRuntimeError, "Wrong Number of Arguments");
|
|
|
|
if (strlen(context->mechanism) == 0)
|
|
mechToUse = mechList;
|
|
else
|
|
mechToUse = context->mechanism;
|
|
|
|
do {
|
|
result = sasl_client_start(context->conn, mechToUse, &interact,
|
|
&response, &len, &chosen);
|
|
if (result == SASL_INTERACT) {
|
|
qsasl_prompt(context, interact);
|
|
}
|
|
} while (result == SASL_INTERACT);
|
|
|
|
if (result != SASL_OK && result != SASL_CONTINUE)
|
|
rb_raise(rb_eRuntimeError, "sasl_client_start failed: %d - %s",
|
|
result, sasl_errdetail(context->conn));
|
|
|
|
if (result == SASL_OK) {
|
|
propResult = sasl_getprop(context->conn, SASL_USERNAME, (const void**) &operName);
|
|
if (propResult == SASL_OK) {
|
|
context->operUserName = (char*) malloc(strlen(operName) + 1);
|
|
strcpy(context->operUserName, operName);
|
|
}
|
|
}
|
|
|
|
return rb_ary_new3(3, INT2NUM(result), rb_str_new(response, len), rb_str_new2(chosen));
|
|
}
|
|
|
|
//
|
|
// Take a step in the SASL exchange (only needed for multi-challenge mechanisms).
|
|
//
|
|
static VALUE qsasl_client_step(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
context_t* context;
|
|
VALUE challenge;
|
|
int result;
|
|
int propResult;
|
|
const char* response;
|
|
const char* operName;
|
|
unsigned int len;
|
|
sasl_interact_t* interact = 0;
|
|
|
|
if (argc == 2) {
|
|
context = (context_t*) argv[0];
|
|
challenge = argv[1];
|
|
}
|
|
else
|
|
rb_raise(rb_eRuntimeError, "Wrong Number of Arguments");
|
|
|
|
do {
|
|
result = sasl_client_step(context->conn,
|
|
RSTRING(challenge)->ptr, RSTRING(challenge)->len,
|
|
&interact, &response, &len);
|
|
if (result == SASL_INTERACT) {
|
|
qsasl_prompt(context, interact);
|
|
}
|
|
} while (result == SASL_INTERACT);
|
|
|
|
if (result != SASL_OK && result != SASL_CONTINUE)
|
|
return QSASL_FAILED;
|
|
|
|
if (result == SASL_OK) {
|
|
propResult = sasl_getprop(context->conn, SASL_USERNAME, (const void**) &operName);
|
|
if (propResult == SASL_OK) {
|
|
context->operUserName = (char*) malloc(strlen(operName) + 1);
|
|
strcpy(context->operUserName, operName);
|
|
}
|
|
}
|
|
|
|
return rb_ary_new3(2, INT2NUM(result), rb_str_new(response, len));
|
|
}
|
|
|
|
static VALUE qsasl_user_id(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
context_t* context;
|
|
|
|
if (argc == 1) {
|
|
context = (context_t*) argv[0];
|
|
} else {
|
|
rb_raise(rb_eRuntimeError, "Wrong Number of Arguments");
|
|
}
|
|
|
|
if (context->operUserName)
|
|
return rb_str_new2(context->operUserName);
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
//
|
|
// Encode transport data for the security layer.
|
|
//
|
|
static VALUE qsasl_encode(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
context_t* context;
|
|
VALUE clearText;
|
|
const char* outBuffer;
|
|
unsigned int outSize;
|
|
int result;
|
|
|
|
if (argc == 2) {
|
|
context = (context_t*) argv[0];
|
|
clearText = argv[1];
|
|
}
|
|
else
|
|
rb_raise(rb_eRuntimeError, "Wrong Number of Arguments");
|
|
|
|
result = sasl_encode(context->conn,
|
|
RSTRING(clearText)->ptr, RSTRING(clearText)->len,
|
|
&outBuffer, &outSize);
|
|
if (result != SASL_OK)
|
|
rb_raise(rb_eRuntimeError, "sasl_encode failed: %d - %s",
|
|
result, sasl_errdetail(context->conn));
|
|
|
|
return rb_str_new(outBuffer, outSize);
|
|
}
|
|
|
|
//
|
|
// Decode transport data for the security layer.
|
|
//
|
|
static VALUE qsasl_decode(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
context_t* context;
|
|
VALUE cipherText;
|
|
const char* outBuffer;
|
|
unsigned int outSize;
|
|
int result;
|
|
|
|
if (argc == 2) {
|
|
context = (context_t*) argv[0];
|
|
cipherText = argv[1];
|
|
}
|
|
else
|
|
rb_raise(rb_eRuntimeError, "Wrong Number of Arguments");
|
|
|
|
result = sasl_decode(context->conn,
|
|
RSTRING(cipherText)->ptr, RSTRING(cipherText)->len,
|
|
&outBuffer, &outSize);
|
|
if (result != SASL_OK)
|
|
rb_raise(rb_eRuntimeError, "sasl_decode failed: %d - %s",
|
|
result, sasl_errdetail(context->conn));
|
|
|
|
return rb_str_new(outBuffer, outSize);
|
|
}
|
|
|
|
//
|
|
// Initialize the Sasl module.
|
|
//
|
|
void Init_sasl()
|
|
{
|
|
mSasl = rb_define_module("Sasl");
|
|
|
|
rb_define_module_function(mSasl, "client_init", qsasl_client_init, -1);
|
|
rb_define_module_function(mSasl, "client_new", qsasl_client_new, -1);
|
|
rb_define_module_function(mSasl, "free", qsasl_free, -1);
|
|
rb_define_module_function(mSasl, "client_start", qsasl_client_start, -1);
|
|
rb_define_module_function(mSasl, "client_step", qsasl_client_step, -1);
|
|
rb_define_module_function(mSasl, "user_id", qsasl_user_id, -1);
|
|
rb_define_module_function(mSasl, "encode", qsasl_encode, -1);
|
|
rb_define_module_function(mSasl, "decode", qsasl_decode, -1);
|
|
}
|