PAM-IPMI module - stores special users password

Special group('ipmi') user's passwords are stored
by pam_ipmicheck and pam_ipmisave module.
pam_ipmicheck will check password restrictions
for the special group user and pam_ipmisave
will update the password in special password
file in encrypted form.
Note: These modules still needs pam_unix support
to store the hashed password and work along
with pam_unix in stacked module concept

Change-Id: Id959107cae1819ff622e5993cd276075bcb92d2a
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..73b6b07
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,45 @@
+How to use this list:
+    Find the most specific section entry (described below) that matches where
+    your change lives and add the reviewers (R) and maintainers (M) as
+    reviewers. You can use the same method to track down who knows a particular
+    code base best.
+
+    Your change/query may span multiple entries; that is okay.
+
+    If you do not find an entry that describes your request at all, someone
+    forgot to update this list; please at least file an issue or send an email
+    to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+    Section entries are structured according to the following scheme:
+
+    X:  NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+    X:  ...
+    .
+    .
+    .
+
+    Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+    organization; FILE_PATH is a file path within the repository, possibly with
+    wildcards; X is a tag of one of the following types:
+
+    M:  Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+        if omitted from an entry, assume one of the maintainers from the
+        MAINTAINERS entry.
+    R:  Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+        these people are to be added as reviewers for a change matching the repo
+        path.
+    F:  Denotes forked from an external repository; has fields URL.
+
+    Line comments are to be denoted "# SOME COMMENT" (typical shell style
+    comment); it is important to follow the correct syntax and semantics as we
+    may want to use automated tools with this file in the future.
+
+    A change cannot be added to an OpenBMC repository without a MAINTAINER's
+    approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+M:  Richard Thomaiyar <richard.marian.thomaiyar@linux.intel.com> <rthomaiy123!>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..c979d72
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,12 @@
+CLEANFILES = *~
+
+securelibdir = /lib/security
+securelib_LTLIBRARIES = pam_ipmicheck.la pam_ipmisave.la
+
+pam_ipmicheck_la_SOURCES = src/pam_ipmicheck/pam_ipmicheck.c
+pam_ipmicheck_la_LDFLAGS = -module -avoid-version -shared
+pam_ipmicheck_LDADD = $(LIBPAM) $(LIBCRYPT)
+
+pam_ipmisave_la_SOURCES = src/pam_ipmisave/pam_ipmisave.c
+pam_ipmisave_la_LDFLAGS = -module -avoid-version -shared -lssl
+pam_ipmisave_LDADD = $(LIBPAM) $(LIBCRYPT)
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..50b75b7
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+        config.guess config.h.in config.sub configure depcomp install-sh \
+        ltmain.sh missing *libtool test-driver"
+
+case $1 in
+    clean)
+        test -f Makefile && make maintainer-clean
+        for file in ${AUTOCONF_FILES}; do
+            find -name "$file" | xargs -r rm -rf
+        done
+        exit 0
+        ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b2f823e
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,21 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([pam_ipmi], [0.1], [https://github.com/openbmc/pam-ipmi/issues])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall foreign dist-xz])
+AM_SILENT_RULES([yes])
+
+AC_SUBST(PACKAGE)
+AC_SUBST(VERSION)
+
+# Checks for programs
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Checks for library functions.
+LT_INIT # Removes 'unrecognized options: --with-libtool-sysroot
+
+# Create configured output
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/src/.clang-format b/src/.clang-format
new file mode 100644
index 0000000..83b7778
--- /dev/null
+++ b/src/.clang-format
@@ -0,0 +1,21 @@
+---
+BasedOnStyle: LLVM
+Language: C
+IndentWidth: 8
+UseTab: Always
+BreakBeforeBraces: Linux
+AlwaysBreakBeforeMultilineStrings: true
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+IndentCaseLabels: false
+AlignEscapedNewlinesLeft: false
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AlignAfterOpenBracket: true
+SpaceAfterCStyleCast: false
+MaxEmptyLinesToKeep: 2
+BreakBeforeBinaryOperators: NonAssignment
+BreakStringLiterals: false
+SortIncludes:    false
+ContinuationIndentWidth: 8
diff --git a/src/pam_ipmicheck/pam_ipmicheck.c b/src/pam_ipmicheck/pam_ipmicheck.c
new file mode 100644
index 0000000..41b3abf
--- /dev/null
+++ b/src/pam_ipmicheck/pam_ipmicheck.c
@@ -0,0 +1,124 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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 <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include <security/pam_ext.h>
+#include <security/pam_modules.h>
+#include <security/pam_modutil.h>
+
+#define MAX_SPEC_GRP_PASS_LENGTH 20
+#define MAX_SPEC_GRP_USER_LENGTH 16
+
+
+/*
+ * This module is intended to verify special group user password matches the
+ * restrictions needed.
+ *
+ * Note: Other than for pam_chauthtok(), pam_ipmicheck module should not be
+ * used for other purpose like authentication, session & account management.
+ * This module has to be used along with pam_ipmisave module, which will save
+ * the passwords of the special group users.
+ */
+
+
+static const char *get_option(const pam_handle_t *pamh, const char *option,
+			      int argc, const char **argv)
+{
+	int i = 0;
+	size_t len = strlen(option);
+
+	for (i = 0; i < argc; ++i) {
+		if (strncmp(option, argv[i], len) == 0) {
+			if (argv[i][len] == '=') {
+				return &argv[i][len + 1];
+			}
+		}
+	}
+	return NULL;
+}
+
+/* Password Management API's */
+
+int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
+{
+	int retval = -1;
+	const void *item = NULL;
+	const char *user = NULL;
+	const char *pass_new = NULL, *pass_old = NULL;
+	const char *spec_grp_name =
+		get_option(pamh, "spec_grp_name", argc, argv);
+
+	pam_syslog(pamh, LOG_DEBUG, "Special group name is %s", spec_grp_name);
+
+	if (spec_grp_name == NULL) {
+		return PAM_IGNORE;
+	}
+	if (flags & PAM_PRELIM_CHECK) {
+		// send success to verify other stacked modules prelim check.
+		pam_syslog(pamh, LOG_DEBUG, "PRELIM_CHECK Called");
+		return PAM_SUCCESS;
+	}
+
+	// Read new password.
+	// Note: Subsequent modules must use stacked password option use_authtok
+	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass_new, NULL);
+	if (retval != PAM_SUCCESS) {
+		pam_syslog(pamh, LOG_ERR,
+			   "password - unable to get new password");
+		return retval;
+	}
+
+	retval = pam_get_user(pamh, &user, NULL);
+	if (retval != PAM_SUCCESS) {
+		return retval;
+	}
+
+	struct group *grp;
+	int spec_grp_usr = 0;
+	// Verify whether the user belongs to special group.
+	grp = pam_modutil_getgrnam(pamh, spec_grp_name);
+	if (grp != NULL) {
+		while (*(grp->gr_mem) != NULL) {
+			if (strcmp(user, *grp->gr_mem) == 0) {
+				spec_grp_usr = 1;
+				break;
+			}
+			(grp->gr_mem)++;
+		}
+	}
+
+	if (spec_grp_usr) {
+		// verify the new password is acceptable.
+		if (strlen(pass_new) > MAX_SPEC_GRP_PASS_LENGTH
+		    || strlen(user) > MAX_SPEC_GRP_USER_LENGTH) {
+			pam_syslog(
+				pamh, LOG_ERR,
+				"Password length (%x) / User name length (%x) not acceptable",
+				strlen(pass_new), strlen(user));
+			pass_new = pass_old = NULL;
+			return PAM_NEW_AUTHTOK_REQD;
+		}
+	}
+
+	return PAM_SUCCESS;
+}
+
+/* end of module definition */
diff --git a/src/pam_ipmisave/pam_ipmisave.c b/src/pam_ipmisave/pam_ipmisave.c
new file mode 100644
index 0000000..bad10dd
--- /dev/null
+++ b/src/pam_ipmisave/pam_ipmisave.c
@@ -0,0 +1,716 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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 <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <security/pam_modules.h>
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+
+/*
+ * This module is intended to save password of  special group user
+ *
+ */
+
+#define MAX_SPEC_GRP_PASS_LENGTH 20
+#define MAX_SPEC_GRP_USER_LENGTH 16
+#define MAX_KEY_SIZE 8
+#define DEFAULT_SPEC_PASS_FILE "/etc/ipmi_pass"
+#define META_PASSWD_SIG "=OPENBMC="
+#define block_round(ODD, BLK)                                                  \
+	((ODD) + (((BLK) - ((ODD) & ((BLK)-1))) & ((BLK)-1)))
+
+/*
+ * Meta data struct for storing the encrypted password file
+ * Note: Followed by this structure, the real data of hash, iv, encrypted data
+ * with pad and mac are stored.
+ * Decrypted data will hold user name & password for every new line with format
+ * like <user name>:<password>\n
+ */
+typedef struct metapassstruct {
+	char signature[10];
+	unsigned char reseved[2];
+	size_t hashsize;
+	size_t ivsize;
+	size_t datasize;
+	size_t padsize;
+	size_t macsize;
+} metapassstruct;
+
+/**
+ * @brief to acquire lock for atomic operation
+ * Internally uses lckpwdf to acquire the lock. Tries to acquire the lock
+ * using lckpwdf() in interval of 1ms, with maximum of 100 attempts.
+ *
+ * @return PAM_SUCCESS for success / PAM_AUTHTOK_LOCK_BUSY for failure
+ */
+int lock_pwdf(void)
+{
+	int i;
+	int retval;
+
+	i = 0;
+	while ((retval = lckpwdf()) != 0 && i < 100) {
+		usleep(1000);
+		i++;
+	}
+	if (retval != 0) {
+		return PAM_AUTHTOK_LOCK_BUSY;
+	}
+	return PAM_SUCCESS;
+}
+
+/**
+ * @brief unlock the acquired lock
+ * Internally uses ulckpwdf to release the lock
+ */
+void unlock_pwdf(void)
+{
+	ulckpwdf();
+}
+
+/**
+ * @brief to get argument value of option
+ * Function to get the value of argument options.
+ *
+ * @param[in] pamh - pam handle
+ * @param[in] option - argument option to which value has to returned
+ * @param[in] argc - argument count
+ * @param[in] argv - array of arguments
+ */
+static const char *get_option(const pam_handle_t *pamh, const char *option,
+			      int argc, const char **argv)
+{
+	int i;
+	size_t len;
+
+	len = strlen(option);
+
+	for (i = 0; i < argc; ++i) {
+		if (strncmp(option, argv[i], len) == 0) {
+			if (argv[i][len] == '=') {
+				return &argv[i][len + 1];
+			}
+		}
+	}
+	return NULL;
+}
+
+/**
+ * @brief encrypt or decrypt function
+ * Function which will do the encryption or decryption of the data.
+ *
+ * @param[in] pamh - pam handle.
+ * @param[in] isencrypt - encrypt or decrypt option.
+ * @param[in] cipher - EVP_CIPHER to be used
+ * @param[in] key - key which has to be used in EVP_CIPHER api's.
+ * @param[in] keylen - Length of the key.
+ * @param[in] iv - Initialization vector data, used along with key
+ * @param[in] ivlen - Length of IV.
+ * @param[in] inbytes - buffer which has to be encrypted or decrypted.
+ * @param[in] inbyteslen - length of input buffer.
+ * @param[in] outbytes - buffer to store decrypted or encrypted data.
+ * @param[in] outbyteslen - length of output buffer
+ * @param[in/out] mac - checksum to cross verify. Will be verified for decrypt
+ * and returns for encrypt.
+ * @param[in/out] maclen - length of checksum
+ * @return - 0 for success -1 for failures.
+ */
+int encrypt_decrypt_data(const pam_handle_t *pamh, int isencrypt,
+			 const EVP_CIPHER *cipher, const char *key,
+			 size_t keylen, const char *iv, size_t ivlen,
+			 const char *inbytes, size_t inbyteslen, char *outbytes,
+			 size_t *outbyteslen, char *mac, size_t *maclen)
+{
+	EVP_CIPHER_CTX *ctx;
+	const EVP_MD *digest;
+	size_t outEVPlen = 0;
+	int retval = 0;
+	size_t outlen = 0;
+
+	if (cipher == NULL || key == NULL || iv == NULL || inbytes == NULL
+	    || outbytes == NULL || mac == NULL || inbyteslen == 0
+	    || EVP_CIPHER_key_length(cipher) > keylen
+	    || EVP_CIPHER_iv_length(cipher) > ivlen) {
+		pam_syslog(pamh, LOG_DEBUG, "Invalid inputs");
+		return -1;
+	}
+
+	digest = EVP_sha256();
+	if (!isencrypt) {
+		char calmac[EVP_MAX_MD_SIZE];
+		size_t calmaclen = 0;
+		// calculate MAC for the encrypted message.
+		if (NULL
+		    == HMAC(digest, key, keylen, inbytes, inbyteslen, calmac,
+			    &calmaclen)) {
+			pam_syslog(pamh, LOG_DEBUG,
+				   "Failed to verify authentication %d",
+				   retval);
+			return -1;
+		}
+		if (!((calmaclen == *maclen)
+		      && (memcmp(calmac, mac, calmaclen) == 0))) {
+			pam_syslog(pamh, LOG_DEBUG,
+				   "Authenticated message doesn't match %d, %d",
+				   calmaclen, *maclen);
+			return -1;
+		}
+	}
+
+	ctx = EVP_CIPHER_CTX_new();
+	EVP_CIPHER_CTX_set_padding(ctx, 1);
+
+	// Set key & IV
+	retval = EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, isencrypt);
+	if (!retval) {
+		pam_syslog(pamh, LOG_DEBUG, "EVP_CipherInit_ex failed with %d",
+			   retval);
+		EVP_CIPHER_CTX_free(ctx);
+		return -1;
+	}
+	if ((retval = EVP_CipherUpdate(ctx, outbytes + outlen, &outEVPlen,
+				       inbytes, inbyteslen))) {
+		outlen += outEVPlen;
+		if ((retval = EVP_CipherFinal(ctx, outbytes + outlen,
+					      &outEVPlen))) {
+			outlen += outEVPlen;
+			*outbyteslen = outlen;
+		} else {
+			pam_syslog(pamh, LOG_DEBUG,
+				   "EVP_CipherFinal returns with %d", retval);
+			EVP_CIPHER_CTX_free(ctx);
+			return -1;
+		}
+	} else {
+		pam_syslog(pamh, LOG_DEBUG, "EVP_CipherUpdate returns with %d",
+			   retval);
+		EVP_CIPHER_CTX_free(ctx);
+		return -1;
+	}
+	EVP_CIPHER_CTX_free(ctx);
+
+	if (isencrypt) {
+		// Create MAC for the encrypted message.
+		if (NULL
+		    == HMAC(digest, key, keylen, outbytes, *outbyteslen, mac,
+			    maclen)) {
+			pam_syslog(pamh, LOG_DEBUG,
+				   "Failed to create authentication %d",
+				   retval);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+
+/**
+ * @brief get temporary file handle
+ * Function to get the temporary file handle, created using mkstemp
+ *
+ * @param[in] pamh - pam handle.
+ * @param[in/out] tempfilename - tempfilename, which will be used in mkstemp.
+ * @return - FILE handle for success. NULL for failure
+ */
+FILE * get_temp_file_handle(const pam_handle_t *pamh, char * const tempfilename)
+{
+	FILE *tempfile = NULL;
+	int fd = -1;
+	int oldmask = umask(077);
+	fd = mkstemp(tempfilename);
+	if (fd == -1) {
+		pam_syslog(pamh, LOG_DEBUG, "Error in creating temp file");
+		umask(oldmask);
+		return NULL;
+	}
+	pam_syslog(pamh, LOG_DEBUG, "Temporary file name is %s", tempfilename);
+
+	tempfile = fdopen(fd, "w");
+	umask(oldmask);
+	return tempfile;
+}
+
+
+/**
+ * @brief updates special password file
+ * Function to update the special password file. Stores the password against
+ * username in encrypted form along with meta data
+ *
+ * @param[in] pamh - pam handle.
+ * @param[in] keyfilename - file name where key seed is stored.
+ * @param[in] filename - special password file name
+ * @param[in] forwho - name of the user
+ * @param[in] towhat - password that has to stored in encrypted form
+ * @return - PAM_SUCCESS for success or PAM_AUTHTOK_ERR for failure
+ */
+int update_pass_special_file(const pam_handle_t *pamh, const char *keyfilename,
+			     const char *filename, const char *forwho,
+			     const char *towhat)
+{
+	struct stat st;
+	FILE *pwfile = NULL, *opwfile = NULL, *keyfile = NULL;
+	int err = 0, wroteentry = 0;
+	char tempfilename[1024];
+	size_t forwholen = strlen(forwho);
+	size_t towhatlen = strlen(towhat);
+	char keybuff[MAX_KEY_SIZE] = {0};
+	size_t keybuffsize = sizeof(keybuff);
+
+	const EVP_CIPHER *cipher = EVP_aes_128_cbc();
+	const EVP_MD *digest = EVP_sha256();
+
+	char *linebuff = NULL, *opwfilebuff = NULL, *opwptext = NULL;
+	size_t opwptextlen = 0, opwfilesize = 0;
+	metapassstruct *opwmp = NULL;
+
+	char *pwptext = NULL, *pwctext = NULL;
+	size_t pwctextlen = 0, pwptextlen = 0, maclen = 0;
+	size_t writtensize = 0, keylen = 0;
+	metapassstruct pwmp = {META_PASSWD_SIG, {0, 0}, .0, 0, 0, 0, 0};
+	char mac[EVP_MAX_MD_SIZE] = {0};
+	unsigned char key[EVP_MAX_KEY_LENGTH];
+	char iv[EVP_CIPHER_iv_length(cipher)];
+	char hash[EVP_MD_block_size(digest)];
+
+	// Following steps are performed in this function.
+	// Step 1: Create a temporary file - always update temporary file, and
+	// then swap it with original one, only if everything succeded at the end.
+	// Step 2: If file already exists, read the old file and decrypt it in buffer
+	// Step 3: Copy user/password pair from old buffer to new buffer,
+	// and update, if the user already exists with the new password
+	// Step 4: Encrypt the new buffer and write it to the temp file created
+	// at Step 1.
+	// Step 5. rename the temporary file name as special password file.
+
+	// verify the tempfilename buffer is enough to hold
+	// filename_XXXXXX (+1 for null).
+	if (strlen(filename)
+	    > (sizeof(tempfilename) - strlen("__XXXXXX") - 1)) {
+		pam_syslog(pamh, LOG_DEBUG, "Not enough buffer, bailing out");
+		return PAM_AUTHTOK_ERR;
+	}
+	// Fetch the key from key file name.
+	keyfile = fopen(keyfilename, "r");
+	if (keyfile == NULL) {
+		pam_syslog(pamh, LOG_DEBUG, "Unable to open key file %s",
+			   keyfilename);
+		return PAM_AUTHTOK_ERR;
+	}
+	if (fread(keybuff, 1, keybuffsize, keyfile) != keybuffsize) {
+		pam_syslog(pamh, LOG_DEBUG, "Key file read failed");
+		fclose(keyfile);
+		return PAM_AUTHTOK_ERR;
+	}
+	fclose(keyfile);
+
+	// Step 1: Try to create a temporary file, in which all the update will happen
+	// then it will be renamed to the original file. This is done to have
+	// atomic operation.
+	snprintf(tempfilename, sizeof(tempfilename), "%s__XXXXXX", filename);
+	pwfile = get_temp_file_handle(pamh, tempfilename);
+	if (pwfile == NULL) {
+		err = 1;
+		goto done;
+	}
+
+	// Update temporary file stat by reading the special password file
+	opwfile = fopen(filename, "r");
+	if (opwfile != NULL) {
+		if (fstat(fileno(opwfile), &st) == -1) {
+			fclose(opwfile);
+			fclose(pwfile);
+			err = 1;
+			goto done;
+		}
+	} else { // Create with this settings if file is not present.
+		memset(&st, 0, sizeof(st));
+		st.st_mode = 0x8000 | S_IRUSR;
+	}
+	if ((fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) ||
+	    (fchmod(fileno(pwfile), st.st_mode) == -1)) {
+		if (opwfile != NULL) {
+			fclose(opwfile);
+		}
+		fclose(pwfile);
+		err = 1;
+		goto done;
+	}
+	opwfilesize = st.st_size;
+
+	// Step 2: Read existing special password file and decrypt the data.
+	if (opwfilesize) {
+		opwfilebuff = malloc(opwfilesize);
+		if (opwfilebuff == NULL) {
+			fclose(opwfile);
+			fclose(pwfile);
+			err = 1;
+			goto done;
+		}
+
+		if (fread(opwfilebuff, 1, opwfilesize, opwfile)) {
+			opwmp = (metapassstruct *)opwfilebuff;
+			opwptext = malloc(opwmp->datasize + opwmp->padsize);
+			if (opwptext == NULL) {
+				free(opwfilebuff);
+				fclose(opwfile);
+				fclose(pwfile);
+				err = 1;
+				goto done;
+			}
+			// User & password pairs are mapped as <user name>:<password>\n.
+			// Add +3 for special chars ':', '\n' and '\0'.
+			pwptextlen = opwmp->datasize + forwholen + towhatlen + 3
+				     + EVP_CIPHER_block_size(cipher);
+			pwptext = malloc(pwptextlen);
+			if (pwptext == NULL) {
+				free(opwptext);
+				free(opwfilebuff);
+				fclose(opwfile);
+				fclose(pwfile);
+				err = 1;
+				goto done;
+			}
+
+			// First get the hashed key to decrypt
+			HMAC(digest, keybuff, keybuffsize,
+			     opwfilebuff + sizeof(*opwmp), opwmp->hashsize, key,
+			     &keylen);
+
+			// Skip decryption if there is no data
+			if (opwmp->datasize != 0) {
+				// Do the decryption
+				if (encrypt_decrypt_data(
+					    pamh, 0, cipher, key, keylen,
+					    opwfilebuff + sizeof(*opwmp)
+					    + opwmp->hashsize,
+					    opwmp->ivsize,
+					    opwfilebuff + sizeof(*opwmp)
+					    + opwmp->hashsize
+					    + opwmp->ivsize,
+					    opwmp->datasize + opwmp->padsize,
+					    opwptext, &opwptextlen,
+					    opwfilebuff + sizeof(*opwmp)
+					    + opwmp->hashsize
+					    + opwmp->ivsize
+					    + opwmp->datasize
+					    + opwmp->padsize,
+					    &opwmp->macsize)
+				    != 0) {
+					pam_syslog(pamh, LOG_DEBUG,
+						   "Decryption failed");
+					free(pwptext);
+					free(opwptext);
+					free(opwfilebuff);
+					fclose(opwfile);
+					fclose(pwfile);
+					err = 1;
+					goto done;
+				}
+			}
+
+			// NULL terminate it, before using it in strtok().
+			opwptext[opwmp->datasize] = '\0';
+
+			linebuff = strtok(opwptext, "\n");
+			// Step 3: Copy the existing user/password pair
+			// to the new buffer, and update the password if user
+			// already exists.
+			while (linebuff != NULL) {
+				if ((!strncmp(linebuff, forwho, forwholen))
+				    && (linebuff[forwholen] == ':')) {
+					writtensize += snprintf(
+							       pwptext + writtensize,
+							       pwptextlen - writtensize,
+							       "%s:%s\n", forwho, towhat);
+					wroteentry = 1;
+				} else {
+					writtensize += snprintf(
+							       pwptext + writtensize,
+							       pwptextlen - writtensize,
+							       "%s\n", linebuff);
+				}
+				linebuff = strtok(NULL, "\n");
+			}
+		}
+		// Clear the old password related buffers here, as we are done
+		// with it.
+		free(opwfilebuff);
+		free(opwptext);
+	} else {
+		pwptextlen = forwholen + towhatlen + 3
+			     + EVP_CIPHER_block_size(cipher);
+		pwptext = malloc(pwptextlen);
+		if (pwptext == NULL) {
+			if (opwfile != NULL) {
+				fclose(opwfile);
+			}
+			fclose(pwfile);
+			err = 1;
+			goto done;
+		}
+	}
+
+	if (opwfile != NULL) {
+		fclose(opwfile);
+	}
+
+	if (wroteentry) {
+		// user password pair already updated,  round it off as per the CIPHER block
+		pwptextlen =
+			block_round(writtensize, EVP_CIPHER_block_size(cipher));
+		// memset the padding bytes
+		memset(pwptext + writtensize, 0, pwptextlen - writtensize);
+	} else {
+		// Write the new user:password pair at the end and round it off as per the
+		// CIPHER block.
+		writtensize += snprintf(pwptext + writtensize,
+					pwptextlen - writtensize, "%s:%s\n",
+					forwho, towhat);
+		pwptextlen =
+			block_round(writtensize, EVP_CIPHER_block_size(cipher));
+		// memset the padding bytes
+		memset(pwptext + writtensize, 0, pwptextlen - writtensize);
+	}
+
+	// Step 4: Encrypt the data and write to the temporary file
+	if (RAND_bytes(hash, EVP_MD_block_size(digest)) != 1) {
+		pam_syslog(pamh, LOG_DEBUG,
+			   "Hash genertion failed, bailing out");
+		free(pwptext);
+		fclose(pwfile);
+		err = 1;
+		goto done;
+	}
+
+	// Generate hash key, which will be used for encryption.
+	HMAC(digest, keybuff, keybuffsize, hash, EVP_MD_block_size(digest), key,
+	     &keylen);
+	// Generate IV values
+	if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) {
+		pam_syslog(pamh, LOG_DEBUG,
+			   "IV generation failed, bailing out");
+		free(pwptext);
+		fclose(pwfile);
+		err = 1;
+		goto done;
+	}
+
+	// Buffer to store encrypted message.
+	pwctext = malloc(pwptextlen + EVP_CIPHER_block_size(cipher));
+	if (pwctext == NULL) {
+		pam_syslog(pamh, LOG_DEBUG, "Ctext buffer failed, bailing out");
+		free(pwptext);
+		fclose(pwfile);
+		err = 1;
+		goto done;
+	}
+
+	// Do the encryption
+	if (encrypt_decrypt_data(pamh, 1, cipher, key, keylen, iv,
+				 EVP_CIPHER_iv_length(cipher), pwptext,
+				 pwptextlen, pwctext, &pwctextlen, mac, &maclen)
+	    != 0) {
+		pam_syslog(pamh, LOG_DEBUG, "Encryption failed");
+		free(pwctext);
+		free(pwptext);
+		fclose(pwfile);
+		err = 1;
+		goto done;
+	}
+
+	// Update the meta password structure.
+	pwmp.hashsize = EVP_MD_block_size(digest);
+	pwmp.ivsize = EVP_CIPHER_iv_length(cipher);
+	pwmp.datasize = writtensize;
+	pwmp.padsize = pwctextlen - writtensize;
+	pwmp.macsize = maclen;
+
+	// Write the meta password structure, followed by hash, iv, encrypted
+	// data & mac.
+	if (fwrite(&pwmp, 1, sizeof(pwmp), pwfile) != sizeof(pwmp)) {
+		pam_syslog(pamh, LOG_DEBUG, "Error in writing meta data");
+		err = 1;
+	}
+	if (fwrite(hash, 1, pwmp.hashsize, pwfile) != pwmp.hashsize) {
+		pam_syslog(pamh, LOG_DEBUG, "Error in writing hash data");
+		err = 1;
+	}
+	if (fwrite(iv, 1, pwmp.ivsize, pwfile) != pwmp.ivsize) {
+		pam_syslog(pamh, LOG_DEBUG, "Error in writing IV data");
+		err = 1;
+	}
+	if (fwrite(pwctext, 1, pwctextlen, pwfile) != pwctextlen) {
+		pam_syslog(pamh, LOG_DEBUG, "Error in encrypted data");
+		err = 1;
+	}
+	if (fwrite(mac, 1, maclen, pwfile) != maclen) {
+		pam_syslog(pamh, LOG_DEBUG, "Error in writing MAC");
+		err = 1;
+	}
+
+	free(pwctext);
+	free(pwptext);
+
+	if (fflush(pwfile) || fsync(fileno(pwfile))) {
+		pam_syslog(
+			pamh, LOG_DEBUG,
+			"fflush or fsync error writing entries to special file: %s",
+			tempfilename);
+		err = 1;
+	}
+
+	if (fclose(pwfile)) {
+		pam_syslog(pamh, LOG_DEBUG,
+			   "fclose error writing entries to special file: %s",
+			   tempfilename);
+		err = 1;
+	}
+
+done:
+	if (!err) {
+		// Step 5: Rename the temporary file as special password file.
+		if (!rename(tempfilename, filename)) {
+			pam_syslog(pamh, LOG_DEBUG,
+				   "password changed for %s in special file",
+				   forwho);
+		} else {
+			err = 1;
+		}
+	}
+
+	// Clear out the key buff.
+	memset(keybuff, 0, keybuffsize);
+
+	if (!err) {
+		return PAM_SUCCESS;
+	} else {
+		unlink(tempfilename);
+		return PAM_AUTHTOK_ERR;
+	}
+}
+
+
+/* Password Management API's */
+
+/**
+ * @brief pam_sm_chauthtok API
+ * Function which will be called for pam_chauthtok() calls.
+ *
+ * @param[in] pamh - pam handle
+ * @param[in] flags - pam calls related flags
+ * @param[in] argc - argument counts / options
+ * @param[in] argv - array of arguments / options
+ * @return - PAM_SUCCESS for success, others for failure
+ */
+int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
+{
+	int retval = -1;
+	const void *item = NULL;
+	const char *user = NULL;
+	const char *pass_new = NULL, *pass_old = NULL;
+	const char *spec_grp_name =
+		get_option(pamh, "spec_grp_name", argc, argv);
+	const char *spec_pass_file =
+		get_option(pamh, "spec_pass_file", argc, argv);
+	const char *key_file = get_option(pamh, "key_file", argc, argv);
+
+
+	if (spec_grp_name == NULL || key_file == NULL) {
+		return PAM_IGNORE;
+	}
+	if (flags & PAM_PRELIM_CHECK) {
+		// send success to verify other stacked modules prelim check.
+		return PAM_SUCCESS;
+	}
+
+	retval = pam_get_user(pamh, &user, NULL);
+	if (retval != PAM_SUCCESS) {
+		return retval;
+	}
+
+	// get  already read password by the stacked pam module
+	// Note: If there are no previous stacked pam module which read
+	// the new password, then return with AUTHTOK_ERR
+
+	retval = pam_get_item(pamh, PAM_AUTHTOK, &item);
+	if (retval != PAM_SUCCESS || item == NULL) {
+		return PAM_AUTHTOK_ERR;
+	}
+	pass_new = item;
+
+	struct group *grp;
+	int spec_grp_usr = 0;
+	// Verify whether the user belongs to special group.
+	grp = pam_modutil_getgrnam(pamh, spec_grp_name);
+	if (grp != NULL) {
+		while (*(grp->gr_mem) != NULL) {
+			if (strcmp(user, *grp->gr_mem) == 0) {
+				spec_grp_usr = 1;
+				break;
+			}
+			(grp->gr_mem)++;
+		}
+	}
+
+	pam_syslog(pamh, LOG_DEBUG, "User belongs to special grp: %x",
+		   spec_grp_usr);
+
+	if (spec_grp_usr) {
+		// verify the new password is acceptable.
+		if (strlen(pass_new) > MAX_SPEC_GRP_PASS_LENGTH
+		    || strlen(user) > MAX_SPEC_GRP_USER_LENGTH) {
+			pam_syslog(
+				pamh, LOG_ERR,
+				"Password length (%x) / User name length (%x) not acceptable",
+				strlen(pass_new), strlen(user));
+			pass_new = NULL;
+			return PAM_NEW_AUTHTOK_REQD;
+		}
+		if (spec_pass_file == NULL) {
+			spec_pass_file = DEFAULT_SPEC_PASS_FILE;
+			pam_syslog(
+				pamh, LOG_ERR,
+				"Using default special password file name :%s",
+				spec_pass_file);
+		}
+		if (retval = lock_pwdf()) {
+			pam_syslog(
+				pamh, LOG_ERR,
+				"Failed to lock the passwd file");
+			return retval;
+		}
+		retval = update_pass_special_file(
+				 pamh, key_file, spec_pass_file, user, pass_new);
+		unlock_pwdf();
+		return retval;
+	}
+
+	return PAM_SUCCESS;
+}
+
+/* end of module definition */