Convert HTPasswd code from Go to C

This commit is contained in:
Arthur Barr
2020-10-01 17:15:37 +01:00
committed by Arthur J Barr
parent adbc95c5d5
commit 5fd9fc5e26
16 changed files with 798 additions and 431 deletions

View File

@@ -46,7 +46,6 @@ RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \"
RUN go build ./cmd/chkmqready/ RUN go build ./cmd/chkmqready/
RUN go build ./cmd/chkmqhealthy/ RUN go build ./cmd/chkmqhealthy/
RUN go build ./cmd/runmqdevserver/ RUN go build ./cmd/runmqdevserver/
RUN go build -buildmode=c-shared -o amqpasdev.so ./internal/qmgrauth/pas.go
RUN go test -v ./cmd/runmqdevserver/... RUN go test -v ./cmd/runmqdevserver/...
RUN go test -v ./cmd/runmqserver/ RUN go test -v ./cmd/runmqserver/
RUN go test -v ./cmd/chkmqready/ RUN go test -v ./cmd/chkmqready/
@@ -115,6 +114,28 @@ USER 1001
ENV MQ_CONNAUTH_USE_HTP=false ENV MQ_CONNAUTH_USE_HTP=false
ENTRYPOINT ["runmqserver"] ENTRYPOINT ["runmqserver"]
###############################################################################
# Build stage to build C code for custom authorization service (developer-only)
###############################################################################
FROM registry.redhat.io/rhel8/gcc-toolset-9-toolchain as cbuilder
# The URL to download the MQ installer from in tar.gz format
# This assumes an archive containing the MQ Non-Install packages
ARG MQ_URL
USER 0
# Install the Apache Portable Runtime code (used for htpasswd hash checking)
RUN yum -y install apr-devel apr-util-openssl apr-util-devel
# Install MQ client
COPY install-mq.sh /usr/local/bin/
RUN mkdir /opt/mqm \
&& chmod a+x /usr/local/bin/install-mq.sh \
&& sleep 1 \
&& INSTALL_SDK=1 install-mq.sh \
&& chown -R 1001:root /opt/mqm/*
COPY authservice/ /opt/app-root/src/authservice/
WORKDIR /opt/app-root/src/authservice/mqhtpass
RUN make mqhtpass.so
RUN make htpass_test
############################################################################### ###############################################################################
# Add default developer config # Add default developer config
############################################################################### ###############################################################################
@@ -136,7 +157,7 @@ LABEL io.k8s.description="Simplify, accelerate and facilitate the reliable excha
LABEL base-image=$BASE_IMAGE LABEL base-image=$BASE_IMAGE
LABEL base-image-release=$BASE_TAG LABEL base-image-release=$BASE_TAG
USER 0 USER 0
COPY --from=builder $GO_WORKDIR/amqpas* /opt/mqm/lib64/ COPY --from=cbuilder /opt/app-root/src/authservice/mqhtpass/mqhtpass.so /opt/mqm/lib64/
COPY etc/mqm/*.ini /etc/mqm/ COPY etc/mqm/*.ini /etc/mqm/
COPY etc/mqm/mq.htpasswd /etc/mqm/ COPY etc/mqm/mq.htpasswd /etc/mqm/
RUN chmod 0660 /etc/mqm/mq.htpasswd RUN chmod 0660 /etc/mqm/mq.htpasswd

View File

@@ -0,0 +1,42 @@
# © Copyright IBM Corporation 2017, 2020
#
# Licensed 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.
# Where to find the sample test.
SRC_DIR = src
# Flags passed to the C compiler.
CFLAGS += -std=c11 -fPIC -Wall
log.o : $(SRC_DIR)/log.c $(SRC_DIR)/log.h
gcc $(CFLAGS) -c $(SRC_DIR)/log.c
htpass.o : $(SRC_DIR)/htpass.c $(SRC_DIR)/htpass.h
gcc $(CFLAGS) -c -I /usr/include/apr-1 $^
htpass_test.o : htpass.o log.o
gcc $(CFLAGS) -I /usr/include/apr-1 -L/usr/lib64 -lapr-1 -laprutil-1 $(SRC_DIR)/htpass_test.c $^ -o htpass_test
htpass_test : htpass_test.o
./htpass_test
mqhtpass.so : log.o htpass.o
gcc $(CFLAGS) -I/opt/mqm/inc -D_REENTRANT -L/usr/lib64 -lapr-1 -laprutil-1 -L/opt/mqm/lib64 -lmqm_r -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64 -shared $(SRC_DIR)/mqhtpass.c $^ -o $@
ldd $@
mqhtpass_unittest.o : $(SRC_DIR)/mqhtpass_test.cc \
$(SRC_DIR)/sample1.h $(FUSED_GTEST_H)
gcc $(CFLAGS) -c $(SRC_DIR)/mqhtpass_test.cc
mqhtpass_unittest : mqhtpass.so mqhtpass_test.o gtest-all.o gtest_main.o
gcc $(CFLAGS) $^ -o $@

View File

@@ -0,0 +1,155 @@
/*
© Copyright IBM Corporation 2020
Licensed 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 <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include <linux/limits.h>
#include <apr_general.h>
#include <apr_errno.h>
#include <apr_md5.h>
char * find_hash(char*, char*);
char * find_hash(char *filename, char *user)
{
bool found = false;
FILE *fp;
char *huser;
char *hash;
fp = fopen(filename, "r");
if (fp == NULL)
{
log_errorf("Error %d opening htpasswd file '%s'", errno, filename);
}
if (fp)
{
const size_t line_size = 1024;
char *line = malloc(line_size);
while (fgets(line, line_size, fp) != NULL)
{
huser = strtok(line, ":");
if (strcmp(user, huser) == 0)
{
hash = strtok(NULL, " \r\n\t");
found = true;
break;
}
}
fclose(fp);
// if (line)
// free(line);
// if (huser)
// free(huser);
// if (encPassword)
// free(encPassword);
}
if (!found)
{
hash = NULL;
}
return(hash);
}
bool htpass_authenticate_user(char *filename, char *user, char *password)
{
char *hash = find_hash(filename, user);
bool result = false;
// Use the Apache Portable Runtime utilities to validate the password against the hash.
// Supports multiple hashing algorithms, but we should only be using bcrypt
apr_status_t status = apr_password_validate(password, hash);
// status is usually either APR_SUCCESS or APR_EMISMATCH
if (status == APR_SUCCESS) {
result = true;
log_debugf("Correct password supplied. user=%s", user);
} else {
log_debugf("Incorrect password supplied. user=%s", user);
}
return(result);
}
// bool htpass_authenticate_user(char *filename, char *user, char *password)
// {
// bool result = false;
// FILE *fp;
// // char line[1024];
// char *huser;
// char *hash;
// // size_t len = 0;
// // size_t read;
// // int valid = -1;
// fp = fopen(filename, "r");
// if (fp == NULL)
// {
// log_errorf("Error %d opening htpasswd file '%s'", errno, filename);
// }
// if (fp)
// {
// const size_t line_size = 1024;
// char *line = malloc(line_size);
// while (fgets(line, line_size, fp) != NULL)
// {
// huser = strtok(line, ":");
// if (strcmp(user, huser) == 0)
// {
// hash = strtok(NULL, " \r\n\t");
// log_debugf("Matched user in htpasswd file: user=%s hash=%s*", huser, hash);
// // Use the Apache Portable Runtime utilities to validate the password against the hash.
// // Supports multiple hashing algorithms, but we should only be using bcrypt
// apr_status_t status = apr_password_validate(password, hash);
// // status is usually either APR_SUCCESS or APR_EMISMATCH
// if (status == APR_SUCCESS) {
// result = true;
// log_debugf("Correct password supplied. user=%s", huser);
// } else {
// log_debugf("Incorrect password supplied. user=%s", huser);
// }
// // Break out of the loop, as we've found the right user
// break;
// // TODO: Do we need to free(hash)?
// }
// else
// {
// log_debugf("Read incorrect user in htpassword: user=%s", huser);
// }
// }
// fclose(fp);
// // if (line)
// // free(line);
// // if (huser)
// // free(huser);
// // if (encPassword)
// // free(encPassword);
// }
// return result;
// }
bool htpass_valid_user(char *filename, char *user)
{
char *hash = find_hash(filename, user);
bool valid = false;
if (hash != NULL)
{
valid = true;
}
return(valid);
}

View File

@@ -0,0 +1,23 @@
/*
© Copyright IBM Corporation 2020
Licensed 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.
*/
#ifndef _HTPASS_H
#define _HTPASS_H
_Bool htpass_authenticate_user(char *, char *, char *);
_Bool htpass_valid_user(char *, char *);
#endif

View File

@@ -0,0 +1,71 @@
/*
© Copyright IBM Corporation 2020
Licensed 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 <stdlib.h>
#include "log.h"
#include "htpass.h"
void test_fail(const char *test_name) {
printf("Failed test %s\n", test_name);
exit(1);
}
void test_htpass_authenticate_user_fred_valid()
{
int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd");
printf("%s: fred - %d\n", __func__, ok);
if (!ok) test_fail(__func__);
}
void test_htpass_authenticate_user_fred_invalid1()
{
int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd ");
printf("%s: fred - %d\n", __func__, ok);
if (ok) test_fail(__func__);
}
void test_htpass_authenticate_user_fred_invalid2()
{
int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "");
printf("%s: fred - %d\n", __func__, ok);
if (ok) test_fail(__func__);
}
void test_htpass_authenticate_user_fred_invalid3()
{
int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "clearlywrong");
printf("%s: fred - %d\n", __func__, ok);
if (ok) test_fail(__func__);
}
void test_htpass_authenticate_user_barney_valid()
{
int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "barney", "s3cret");
printf("%s: barney - %d\n", __func__, ok);
if (!ok) test_fail(__func__);
}
int main()
{
log_init_file(stdout);
printf("TESTING BEGINS\n");
test_htpass_authenticate_user_fred_valid();
test_htpass_authenticate_user_fred_invalid1();
test_htpass_authenticate_user_fred_invalid2();
test_htpass_authenticate_user_fred_invalid3();
test_htpass_authenticate_user_barney_valid();
}

View File

@@ -0,0 +1,2 @@
fred:$2y$05$3Fp9epsqEwWOHdyj9Ngf9.qfX34kzc9zNrdQ7kac0GmcCvQjIkAwy
barney:$2y$05$l8EoyCQ9y2PyfUzIDDfTyu7SSaJEYB1TuHy07xZvN7xt/pR3SIw0a

View File

@@ -0,0 +1,166 @@
/*
© Copyright IBM Corporation 2020
Licensed 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 <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#define LOG_LEVEL_INFO 0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_DEBUG 2
FILE *fp = NULL;
int pid;
int log_init(char *filename)
{
int result = 0;
pid = getpid();
if (!fp)
{
fp = fopen(filename, "a");
if (fp)
{
setbuf(fp, NULL);
}
else
{
result = 1;
}
}
return result;
}
void log_init_file(FILE *f)
{
fp = f;
}
void log_close()
{
if (fp)
{
fclose(fp);
fp = NULL;
}
}
void log_printf(const char *source_file, int source_line, const char *level, const char *format, ...)
{
if (fp)
{
char buff[70];
struct tm *utc;
time_t t;
struct timeval now;
gettimeofday(&now, NULL);
t = now.tv_sec;
t = time(NULL);
utc = gmtime(&t);
fprintf(fp, "{");
fprintf(fp, "\"loglevel\":\"%s\"", level);
// Print ISO-8601 time and date
if (strftime(buff, sizeof buff, "%FT%T", utc))
{
fprintf(fp, ", \"ibm_datetime\":\"%s.%3ld", buff, now.tv_usec);
}
fprintf(fp, ", \"ibm_processId\":\"%d\"", pid);
fprintf(fp, ", \"module\":\"%s:%d\"", source_file, source_line);
fprintf(fp, ", \"message\":\"");
// Print log message, using varargs
va_list args;
va_start(args, format);
vfprintf(fp, format, args);
va_end(args);
fprintf(fp, "\"}\n");
}
}
/**
* Writes a message to the log file, using the specified type, based on a printf format string.
*/
// void log_printf(const char *level, const char *format, va_list args)
// {
// // FindSize();
// if (fp)
// {
// char buff[70];
// struct tm *utc;
// time_t t;
// struct timeval now;
// gettimeofday(&now, NULL);
// t = now.tv_sec;
// // Print ISO-8601 time and date
// t = time(NULL);
// utc = gmtime(&t);
// fprintf(fp, "{");
// fprintf(fp, "\"loglevel\":\"%s\"", level);
// if (strftime(buff, sizeof buff, "%FT%T", utc))
// {
// fprintf(fp, ", \"ibm_datetime\":\"%s.%3ld", buff, now.tv_usec);
// }
// fprintf(fp, ", \"ibm_processId\": \"%d\"", pid);
// fprintf(fp, ", \"message\":\"");
// // Print log message, using varargs
// // va_list args;
// // va_start(args, format);
// vfprintf(fp, format, args);
// // va_end(args);
// fprintf(fp, "\"}\n");
// }
// }
// void log_errorf(const char *format, ...)
// {
// va_list args;
// va_start(args, format);
// log_printf("ERROR", format, args);
// va_end(args);
// }
// void log_infof(const char *format, ...)
// {
// va_list args;
// va_start(args, format);
// log_printf("INFO", format, args);
// va_end(args);
// }
// void log_debugf(const char *format, ...)
// {
// va_list args;
// va_start(args, format);
// log_printf("DEBUG", format, args);
// va_end(args);
// }
// void log_debugf2(const char *source_file, const char *source_line, const char *format, ...)
// {
// va_list args;
// va_start(args, format);
// log_printf(source_line, format, args);
// va_end(args);
// }

View File

@@ -0,0 +1,50 @@
/*
© Copyright IBM Corporation 2020
Licensed 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.
*/
#ifndef _LOG_H
#define _LOG_H
int log_init(char *);
/**
* Initialize the log wih an existing file handle
*/
void log_init_file(FILE *);
/**
* Write a message to the log file, based on a printf format string.
*/
void log_printf(const char*, int, const char*, const char*, ...);
void log_close();
/**
* Variadic macro to write an informational message to the log file, based on a printf format string.
*/
#define log_infof(format,...) log_printf(__FILE__, __LINE__, "INFO", format, ##__VA_ARGS__)
/**
* Variadic macro to write an error message to the log file, based on a printf format string.
*/
#define log_errorf(format,...) log_printf(__FILE__, __LINE__, "ERROR", format, ##__VA_ARGS__)
/**
* Variadic macro to write a debug message to the log file, based on a printf format string.
*/
#define log_debugf(format,...) log_printf(__FILE__, __LINE__, "DEBUG", format, ##__VA_ARGS__)
#endif

View File

@@ -0,0 +1,262 @@
/*
© Copyright IBM Corporation 2020
Licensed 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.
*/
// This is a developer only configuration and not recommended for production usage.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmqec.h>
#include "log.h"
#include "htpass.h"
/****************************************************************************/
/* Declare the internal functions that implement the interface */
/****************************************************************************/
MQZ_INIT_AUTHORITY MQStart;
static MQZ_AUTHENTICATE_USER mqhtpass_authenticate_user;
static MQZ_FREE_USER mqhtpass_free_user;
static MQZ_TERM_AUTHORITY mqhtpass_term_auth;
#define LOG_FILE "/var/mqm/errors/mqhtpass.log"
#define HTPASSWD_FILE "/etc/mqm/mq.htpasswd"
static char *trim(char *s);
/**
* Initialization and entrypoint for the dynamically loaded
* authorization installable service. It registers the addresses of the
* other functions which are to be called by the queue manager.
*
* This function is called whenever the module is loaded. The Options
* field will show whether it's a PRIMARY (i.e. during qmgr startup) or
* SECONDARY (any other time - normally during the start of an agent
* process which is not necessarily the same as during MQCONN, especially
* when running multi-threaded agents) initialization, but there's
* nothing different that we'd want to do here based on that flag.
*
* Because of when the init function is called, there is no need to
* worry about multi-threaded stuff in this particular function.
*/
void MQENTRY MQStart(
MQHCONFIG hc,
MQLONG Options,
MQCHAR48 QMgrName,
MQLONG ComponentDataLength,
PMQBYTE ComponentData,
PMQLONG Version,
PMQLONG pCompCode,
PMQLONG pReason)
{
MQLONG CC = MQCC_OK;
MQLONG Reason = MQRC_NONE;
CC = log_init(LOG_FILE);
if (CC == MQCC_OK)
{
log_infof("MQStart options=%s qmgr=%s", ((Options == MQZIO_SECONDARY) ? "Secondary" : "Primary"), trim(QMgrName));
}
/************************************************************************/
/* Initialize the entry point vectors. This is performed for both */
/* global and process initialisation, i.e whatever the value of the */
/* Options field. */
/************************************************************************/
if (CC == MQCC_OK)
hc->MQZEP_Call(hc, MQZID_INIT_AUTHORITY, (PMQFUNC)MQStart, &CC, &Reason);
if (CC == MQCC_OK)
hc->MQZEP_Call(hc, MQZID_TERM_AUTHORITY, (PMQFUNC)mqhtpass_term_auth, &CC, &Reason);
if (CC == MQCC_OK)
hc->MQZEP_Call(hc, MQZID_AUTHENTICATE_USER, (PMQFUNC)mqhtpass_authenticate_user, &CC, &Reason);
if (CC == MQCC_OK)
hc->MQZEP_Call(hc, MQZID_FREE_USER, (PMQFUNC)mqhtpass_free_user, &CC, &Reason);
*Version = MQZAS_VERSION_5;
*pCompCode = CC;
*pReason = Reason;
return;
}
/**
* Called during the connection of any application. This allows the OAM
* to change the userid associated with the connection, regardless of the
* operating system user ID. One reason you might want to do that is to
* deal with non-standard user IDs, which perhaps are longer than 12
* characters. The CorrelationPtr can be assigned in this function to
* point to some OAM-managed storage, and is available as part of the
* MQZED structure for all subsequent functions. Note that there is only
* one CorrelPtr stored for the user's hconn, so if two OAMs are chained
* and both want to manage storage for the connection, there would be
* difficulties as there is no reverse call that would allow the second
* to reset the first's pointer (or vice versa). I'd suggest instead
* using something like thread-specific storage as each thread is tied
* to the hconn.
*
* When a clntconn/svrconn channel connects to the queue manager, the
* authentication is supposed to take two stages. First as the
* channel program connects, and then as the MCAUSER is set. You will
* see this as "initial" and "change" context values in the parameters.
*/
static void MQENTRY mqhtpass_authenticate_user(
PMQCHAR pQMgrName,
PMQCSP pSecurityParms,
PMQZAC pApplicationContext,
PMQZIC pIdentityContext,
PMQPTR pCorrelationPtr,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
char *spuser = NULL;
char *sppass = NULL;
if ((pSecurityParms->AuthenticationType) == MQCSP_AUTH_USER_ID_AND_PWD)
{
// Authenticating a user ID and password.
// Firstly, create null-terminated strings from the user credentials in the MQ CSP object
spuser = malloc(pSecurityParms->CSPUserIdLength + 1);
strncpy(spuser, pSecurityParms->CSPUserIdPtr, pSecurityParms->CSPUserIdLength);
spuser[pSecurityParms->CSPUserIdLength] = 0;
sppass = malloc(pSecurityParms->CSPPasswordLength + 1);
strncpy(sppass, pSecurityParms->CSPPasswordPtr, pSecurityParms->CSPPasswordLength);
sppass[pSecurityParms->CSPPasswordLength] = 0;
log_debugf("%s with CSP user set. user=%s", __func__, spuser);
bool authenticated = htpass_authenticate_user(HTPASSWD_FILE, spuser, sppass);
if (authenticated)
{
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
memcpy(pIdentityContext->UserIdentifier, spuser, sizeof(pIdentityContext->UserIdentifier));
log_debugf("Authenticated user=%s", pIdentityContext->UserIdentifier);
}
else
{
// Return a warning, which indicates to MQ that this authorization service hasn't
// authenticated the user, but other authorization services should be tried as well.
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
log_debugf(
"Failed to authenticate user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d",
pIdentityContext->UserIdentifier,
pApplicationContext->EffectiveUserID,
pApplicationContext->ApplName,
spuser,
*pCompCode,
*pReason);
}
free(spuser);
free(sppass);
}
else
{
// Password not supplied, so just check that the user ID is valid
spuser = malloc(sizeof(PMQCHAR12));
strncpy(spuser, pApplicationContext->EffectiveUserID, strlen(pApplicationContext->EffectiveUserID));
spuser[sizeof(PMQCHAR12)] = 0;
log_debugf("%s without CSP user set. effectiveuid=%s", __func__, spuser);
bool valid_user = htpass_valid_user(HTPASSWD_FILE, spuser);
if (valid_user)
{
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
memcpy(pIdentityContext->UserIdentifier, spuser, sizeof(pIdentityContext->UserIdentifier));
}
else
{
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
log_debugf(
"Invalid user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d",
pIdentityContext->UserIdentifier,
pApplicationContext->EffectiveUserID,
pApplicationContext->ApplName,
spuser,
*pCompCode,
*pReason);
}
}
return;
}
/**
* Called during MQDISC, as the inverse of the Authenticate. If the authorization
* service has allocated private storage to hold additional information about
* the user, then this is the time to free it. No more calls will be made
* to the authorization service for this connection instance of this user.
*/
static void MQENTRY mqhtpass_free_user(
PMQCHAR pQMgrName,
PMQZFP pFreeParms,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
log_infof("mqhtpass_freeuser()");
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
}
/**
* Called during MQDISC, as the inverse of the Authenticate. If the OAM
* has allocated private storage to hold additional information about
* the user, then this is the time to free it. No more calls will be made
* to the authorization service for this connection instance of this user.
*/
static void MQENTRY mqhtpass_term_auth(
MQHCONFIG hc,
MQLONG Options,
PMQCHAR pQMgrName,
PMQBYTE pComponentData,
PMQLONG pCompCode,
PMQLONG pReason)
{
log_infof("mqhtpass_term_auth()");
if ((Options & MQZTO_PRIMARY) == MQZTO_PRIMARY)
{
log_close();
}
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
}
/**
* Remove trailing spaces from a string.
*/
static char *trim(char *s)
{
int i;
for (i = strlen(s) - 1; i >= 0; i--)
{
if (s[i] == ' ')
s[i] = 0;
else
break;
}
return s;
}

View File

@@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"syscall" "syscall"
"github.com/ibm-messaging/mq-container/internal/htpasswd" "github.com/ibm-messaging/mq-container/internal/htpasswd"
@@ -30,27 +29,6 @@ import (
var log *logger.Logger var log *logger.Logger
func setPassword(user string, password string) error {
// #nosec G204
cmd := exec.Command("sudo", "chpasswd")
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
fmt.Fprintf(stdin, "%s:%s", user, password)
err = stdin.Close()
if err != nil {
log.Errorf("Error closing password stdin: %v", err)
}
out, err := cmd.CombinedOutput()
if err != nil {
// Include the command output in the error
return fmt.Errorf("%v: %v", err.Error(), out)
}
log.Printf("Set password for \"%v\" user", user)
return nil
}
func getLogFormat() string { func getLogFormat() string {
return os.Getenv("LOG_FORMAT") return os.Getenv("LOG_FORMAT")
} }

View File

@@ -1,7 +1,7 @@
ServiceComponent: ServiceComponent:
Service=AuthorizationService Service=AuthorizationService
Name=Dev.HtpAuth.Service Name=Dev.HtpAuth.Service
Module=/opt/mqm/lib64/amqpasdev.so Module=/opt/mqm/lib64/mqhtpass.so
ComponentDataSize=0 ComponentDataSize=0
ServiceComponent: ServiceComponent:
Service=AuthorizationService Service=AuthorizationService

View File

@@ -25,17 +25,17 @@ test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
if ($UBUNTU); then if ($UBUNTU); then
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get update apt-get update
apt-get install -y --no-install-recommends sudo apt-get install -y --no-install-recommends libaprutil1
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
fi fi
if ($YUM); then if ($YUM); then
yum -y install sudo yum -y install apr-util-openssl
yum -y clean all yum -y clean all
rm -rf /var/cache/yum/* rm -rf /var/cache/yum/*
fi fi
if ($MICRODNF); then if ($MICRODNF); then
microdnf install sudo microdnf install apr-util-openssl
microdnf clean all microdnf clean all
fi fi

View File

@@ -110,44 +110,3 @@ func (htpfile mapHtPasswd) updateHtPasswordFile(isTest bool) error {
} }
return ioutil.WriteFile(file, htpfile.GetBytes(), 0660) return ioutil.WriteFile(file, htpfile.GetBytes(), 0660)
} }
// AuthenticateUser verifies if the given user password match with htpasswrd
func AuthenticateUser(user string, password string, isTest bool) (bool, bool, error) {
passwords := mapHtPasswd(map[string]string{})
if len(strings.TrimSpace(user)) == 0 || len(strings.TrimSpace(password)) == 0 {
return false, false, fmt.Errorf("UserId or Password are empty")
}
err := passwords.ReadHtPasswordFile(isTest)
if err != nil {
return false, false, err
}
ok := false
value, found := passwords[user]
if !found {
return found, ok, fmt.Errorf("User not found in the mq.htpasswd file")
}
err = bcrypt.CompareHashAndPassword([]byte(value), []byte(password))
return found, err == nil, err
}
// ValidateUser validates the given user
func ValidateUser(user string, isTest bool) (bool, error) {
passwords := mapHtPasswd(map[string]string{})
if len(strings.TrimSpace(user)) == 0 {
return false, fmt.Errorf("Userid is empty for AuthenticateUser")
}
err := passwords.ReadHtPasswordFile(isTest)
if err != nil {
return false, err
}
_, found := passwords[strings.TrimSpace(user)]
return found, nil
}

View File

@@ -1,62 +0,0 @@
/*
© Copyright IBM Corporation 2020
Licensed 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.
*/
package htpasswd
import (
"testing"
)
// TestCheckUser verifies Htpassword's use
func TestCheckUser(t *testing.T) {
err := SetPassword("guest", "guestpw", true)
if err != nil {
t.Fatalf("htpassword test failed due to error:%s\n", err.Error())
}
found, ok, err := AuthenticateUser("guest", "guestpw", true)
if err != nil {
t.Fatalf("htpassword test1 failed as user could not be found:%s\n", err.Error())
}
if found == false || ok == false {
t.Fatalf("htpassword test1 failed as user could not be found:%v, ok:%v\n", found, ok)
}
found, ok, err = AuthenticateUser("myguest", "guestpw", true)
if err == nil {
t.Fatalf("htpassword test2 failed as no error received for non-existing user\n")
}
if found == true || ok == true {
t.Fatalf("htpassword test2 failed for non-existing user found :%v, ok:%v\n", found, ok)
}
found, ok, err = AuthenticateUser("guest", "guest", true)
if err == nil {
t.Fatalf("htpassword test3 failed as incorrect password of user did not return error\n")
}
if found == false || ok == true {
t.Fatalf("htpassword test3 failed for existing user with incorrect passwored found :%v, ok:%v\n", found, ok)
}
found, err = ValidateUser("guest", true)
if err != nil || found == false {
t.Fatalf("htpassword test4 failed as user could not be found:%v, ok:%v\n", found, ok)
}
found, err = ValidateUser("myguest", true)
if err != nil || found == true {
t.Fatalf("htpassword test5 failed as non-existing user returned to be found:%v, ok:%v\n", found, ok)
}
}

View File

@@ -1 +0,0 @@
guest:$2y$05$ifFP0nCmFed6.m4iB9CHRuHFps2YeeuwopmOvszWt0GRnN59p8qxW

View File

@@ -1,299 +0,0 @@
/*
© Copyright IBM Corporation 2020
Licensed 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.
*/
//This is a developer only configuration and not recommended for production usage.
package main
/*
#cgo !windows CFLAGS: -I/opt/mqm/lib64 -D_REENTRANT
#cgo !windows,!darwin LDFLAGS: -L/opt/mqm/lib64 -lmqm_r -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64
#cgo darwin LDFLAGS: -L/opt/mqm/lib64 -lmqm_r -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64
#cgo windows CFLAGS: -I"C:/Program Files/IBM/MQ/Tools/c/include"
#cgo windows LDFLAGS: -L "C:/Program Files/IBM/MQ/bin64" -lmqm
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmqc.h>
#include <cmqxc.h>
#include <cmqzc.h>
#include <cmqec.h>
#include <time.h>
static MQZ_INIT_AUTHORITY PASStart;
static MQZ_AUTHENTICATE_USER OAAuthUser;
static MQZ_FREE_USER OAFreeUser;
static MQZ_TERM_AUTHORITY OATermAuth;
extern int Authenticate(char *, char *);
extern int CheckAuthority(char *);
static char *OAEnvStr(MQLONG);
static void FindSize();
static void PrintDateTime();
static FILE *fp = NULL;
static int primary_process = 0;
static void MQENTRY PASStart(
MQHCONFIG hc,
MQLONG Options,
MQCHAR48 QMgrName,
MQLONG ComponentDataLength,
PMQBYTE ComponentData,
PMQLONG Version,
PMQLONG pCompCode,
PMQLONG pReason) {
MQLONG CC = MQCC_OK;
MQLONG Reason = MQRC_NONE;
if ((Options & MQZIO_PRIMARY) == MQZIO_PRIMARY)
primary_process = 1;
fp=fopen("/var/mqm/errors/amqpasdev.log","a");
if (CC == MQCC_OK)
hc->MQZEP_Call(hc, MQZID_INIT_AUTHORITY,(PMQFUNC)PASStart,&CC,&Reason);
if (CC == MQCC_OK)
hc->MQZEP_Call(hc,MQZID_TERM_AUTHORITY,(PMQFUNC)OATermAuth,&CC,&Reason);
if (CC == MQCC_OK)
hc->MQZEP_Call(hc,MQZID_AUTHENTICATE_USER,(PMQFUNC)OAAuthUser,&CC,&Reason);
if (CC == MQCC_OK)
hc->MQZEP_Call(hc,MQZID_FREE_USER,(PMQFUNC)OAFreeUser,&CC,&Reason);
*Version = MQZAS_VERSION_5;
*pCompCode = CC;
*pReason = Reason;
PrintDateTime();
fprintf(fp, "Pluggable OAM Initialized.\n");
fprintf(fp, "THIS IS A DEVELOPER ONLY CONFIGURATION AND NOT RECOMMENDED FOR PRODUCTION USAGE");
return;
}
static char *authuserfmt =
"\tUser : \"%12.12s\"\n"\
"\tEffUser : \"%12.12s\"\n"\
"\tAppName : \"%28.28s\"\n"\
"\tApIdDt : \"%32.32s\"\n"\
"\tEnv : \"%s\"\n"\
"\tApp Pid : %d\n"\
"\tApp Tid : %d\n"\
;
static void MQENTRY OAAuthUser (
PMQCHAR pQMgrName,
PMQCSP pSecurityParms,
PMQZAC pApplicationContext,
PMQZIC pIdentityContext,
PMQPTR pCorrelationPtr,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
char *spuser = NULL;
char *sppass = NULL;
int gorc = MQRC_NOT_AUTHORIZED;
if ((pSecurityParms->CSPUserIdLength) > 0) {
//Grab the user creds from csp.
spuser = malloc(pSecurityParms->CSPUserIdLength+1);
strncpy(spuser,pSecurityParms->CSPUserIdPtr,pSecurityParms->CSPUserIdLength);
spuser[pSecurityParms->CSPUserIdLength]=0;
sppass = malloc(pSecurityParms->CSPPasswordLength+1);
strncpy(sppass,pSecurityParms->CSPPasswordPtr,pSecurityParms->CSPPasswordLength);
sppass[pSecurityParms->CSPPasswordLength]=0;
gorc = Authenticate(spuser,sppass);
if (gorc == MQRC_NONE) {
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
memcpy( pIdentityContext->UserIdentifier
, spuser
, sizeof(pIdentityContext->UserIdentifier) );
} else {
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
//we print to error file only if error'd
PrintDateTime();
if (fp) {
fprintf(fp, authuserfmt,
pIdentityContext->UserIdentifier,
pApplicationContext->EffectiveUserID,
pApplicationContext->ApplName,
pIdentityContext->ApplIdentityData,
OAEnvStr(pApplicationContext->Environment),
pApplicationContext->ProcessId,
pApplicationContext->ThreadId);
fprintf(fp,"\tCSP UserId : %s\n", spuser);
fprintf(fp,"\tCSP Password : %s\n", "****..");
fprintf(fp,"\tPAS-Compcode:%d\n",*pCompCode);
fprintf(fp,"\tPAS-Reasoncode:%d\n",*pReason);
}
}
free(spuser);
free(sppass);
} else {
//this is only a normal UID authentication.
spuser = malloc(sizeof(PMQCHAR12));
strncpy(spuser,pApplicationContext->EffectiveUserID,strlen(pApplicationContext->EffectiveUserID));
spuser[sizeof(PMQCHAR12)]=0;
gorc = CheckAuthority(spuser);
if (gorc == MQRC_NONE){
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
memcpy( pIdentityContext->UserIdentifier
, spuser
, sizeof(pIdentityContext->UserIdentifier) );
} else {
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
//we print only if error'd
PrintDateTime();
if (fp)
{
fprintf(fp, authuserfmt,
pIdentityContext->UserIdentifier,
pApplicationContext->EffectiveUserID,
pApplicationContext->ApplName,
pIdentityContext->ApplIdentityData,
OAEnvStr(pApplicationContext->Environment),
pApplicationContext->ProcessId,
pApplicationContext->ThreadId
);
fprintf(fp,"\tUID : %s\n", spuser);
fprintf(fp,"\tPAS-Compcode:%d\n",*pCompCode);
fprintf(fp,"\tPAS-Reasoncode:%d\n",*pReason);
}
}
}
return;
}
static void MQENTRY OAFreeUser (
PMQCHAR pQMgrName,
PMQZFP pFreeParms,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
return;
}
static void MQENTRY OATermAuth(
MQHCONFIG hc,
MQLONG Options,
PMQCHAR pQMgrName,
PMQBYTE pComponentData,
PMQLONG pCompCode,
PMQLONG pReason)
{
if ((primary_process) && ((Options & MQZTO_PRIMARY) == MQZTO_PRIMARY) ||
((Options & MQZTO_SECONDARY) == MQZTO_SECONDARY))
{
if (fp)
{
fclose(fp);
fp = NULL;
}
}
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
}
static void PrintDateTime() {
FindSize();
struct tm *local;
time_t t;
t = time(NULL);
local = localtime(&t);
if (fp) {
fprintf(fp, "-------------------------------------------------\n");
fprintf(fp, "Local time: %s", asctime(local));
local = gmtime(&t);
fprintf(fp, "UTC time: %s", asctime(local));
}
return;
}
static char *OAEnvStr(MQLONG x)
{
switch (x)
{
case MQXE_OTHER: return "Application";
case MQXE_MCA: return "Channel";
case MQXE_MCA_SVRCONN: return "Channel SvrConn";
case MQXE_COMMAND_SERVER: return "Command Server";
case MQXE_MQSC: return "MQSC";
default: return "Invalid Environment";
}
}
static void FindSize()
{
int sz = 0;
int prev=ftell(fp);
fseek(fp, 0L, SEEK_END);
sz=ftell(fp);
//if log file size goes over 1mb, rewind it.
if (sz > 1000000) {
rewind(fp);
} else {
fseek(fp, prev, SEEK_SET);
}
}
*/
import "C"
import "github.com/ibm-messaging/mq-container/internal/htpasswd"
//export MQStart
func MQStart(hc C.MQHCONFIG, Options C.MQLONG, QMgrName C.PMQCHAR, ComponentDataLength C.MQLONG, ComponentData C.PMQBYTE, Version C.PMQLONG, pCompCode C.PMQLONG, pReason C.PMQLONG) {
C.PASStart(hc, Options, QMgrName, ComponentDataLength, ComponentData, Version, pCompCode, pReason)
}
//export Authenticate
func Authenticate(x *C.char, y *C.char) C.int {
user := C.GoString(x)
pwd := C.GoString(y)
found, ok, err := htpasswd.AuthenticateUser(user, pwd, false)
if !found || !ok || err != nil {
return C.MQRC_UNKNOWN_OBJECT_NAME
}
return C.MQRC_NONE
}
//export CheckAuthority
func CheckAuthority(x *C.char) C.int {
user := C.GoString(x)
found, err := htpasswd.ValidateUser(user, false)
if !found || err != nil {
return C.MQRC_UNKNOWN_OBJECT_NAME
}
return C.MQRC_NONE
}
func main() {}