summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPatrick Murphy <patrick.muprphy@multitech.com>2020-04-13 13:24:41 -0500
committerPatrick Murphy <patrick.muprphy@multitech.com>2020-04-13 13:24:41 -0500
commitf8ba84f1366cc4df61b8f16afd8ed872db8930cc (patch)
tree3c925b228d17188ad99bb86ebf0ce6f88bf89d7c /src
parentf895e46a542d07fec1cdff621ff250b156f0069e (diff)
downloadcommissioning-f8ba84f1366cc4df61b8f16afd8ed872db8930cc.tar.gz
commissioning-f8ba84f1366cc4df61b8f16afd8ed872db8930cc.tar.bz2
commissioning-f8ba84f1366cc4df61b8f16afd8ed872db8930cc.zip
fcgi commissioning 1.0
Diffstat (limited to 'src')
-rw-r--r--src/Makefile24
-rw-r--r--src/commission_func.cc395
-rw-r--r--src/commission_func.h180
-rw-r--r--src/fcgicommissioning.cc32
4 files changed, 631 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..69b2aa8
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,24 @@
+
+CFLAGS=-I.
+DEPS = commission_func.h
+OBJ = fcgicommissioning.o commission_func.o
+LIBS := -ljsoncpp -lfcgi++ -lfcgi
+
+%.o: %.cc $(DEPS)
+ $(CXX) -c -o $@ $< $(CFLAGS)
+
+commissioning.fcgi: $(OBJ)
+ $(CXX) $(LDFLAGS) -o $@ $^ $(CFLAGS) $(LIBS)
+
+install:
+ install -d $(DESTDIR)/usr/bin
+ install -m 0755 commissioning.fcgi $(DESTDIR)/usr/bin
+
+.PHONY : clean
+
+clean:
+ -$(RM) $(OBJ)
+
+
+
+
diff --git a/src/commission_func.cc b/src/commission_func.cc
new file mode 100644
index 0000000..10e47cf
--- /dev/null
+++ b/src/commission_func.cc
@@ -0,0 +1,395 @@
+#include "commission_func.h"
+
+int begin_fcgi() {
+
+ // Set up commissioning enviornment variables.
+
+ bool parseJson, is_err, commissioning = false, complete = false;
+ int popen_ret, atmpt_cntr = 0;
+ Json::Reader reader;
+ Json::Value j_post;
+ Json::StyledWriter swriter;
+ std::string posted, parse_cmd, parse_logger;
+ std::string aasid = "";
+ std::string tmp_usrname = "";
+ std::string tmp_pass = "";
+ std::streambuf * cin_streambuf = std::cin.rdbuf();
+ std::streambuf * cout_streambuf = std::cout.rdbuf();
+ std::streambuf * cerr_streambuf = std::cerr.rdbuf();
+ FILE *fpopen;
+ char fileread[MAX_GET];
+
+ //set up the syslog event
+ openlog("commissioning.fcgi", LOG_PID | LOG_PERROR, LOG_LOCAL0);
+
+ //set up fcgi environment
+ FCGX_Request request;
+
+ FCGX_Init();
+ FCGX_InitRequest(&request, 0, 0);
+
+ //listen for requests
+ while (FCGX_Accept_r(&request) == 0 && !complete) {
+
+ // set streambuffers to fcgi request
+ fcgi_streambuf cin_fcgi_streambuf(request.in);
+ fcgi_streambuf cout_fcgi_streambuf(request.out);
+ fcgi_streambuf cerr_fcgi_streambuf(request.err);
+
+ std::cin.rdbuf(&cin_fcgi_streambuf);
+ std::cout.rdbuf(&cout_fcgi_streambuf);
+ std::cerr.rdbuf(&cerr_fcgi_streambuf);
+
+
+ // receive data from POST
+ posted = get_request_content(request);
+
+
+ if (posted.length() == 0) {
+ //standard output (check if commissioning is on)
+ std::cout << confirmCommissioning();
+ } else {
+ //receive POSTed Json data
+ bool parseJson = reader.parse(posted, j_post);
+
+ //error state
+ if (!parseJson) {
+ syslog (LOG_ALERT, "Received unparsible data");
+ std::cout << ERROR_PARSE;
+ std::cout << "Content-type: text/plain"
+ << "\r\n"
+ << "Output : "
+ << posted
+ <<"\r\n";
+
+ //if sent data doesn't follow proper formatting
+ } else if (!properInput(j_post)) {
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_MALDATA, AASTYPE_ERR);
+ syslog (LOG_ALERT, "Received malformed user data entry");
+
+ //if we are not in the middle of a commissioning session
+ } else if (!commissioning) {
+
+ //if the user requests an illegal username
+ if(!legalName(j_post["username"].asString())) {
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_ILLEGALUSRNAME, AASTYPE_ERR);
+ syslog (LOG_ALERT, "Received request for unauthorized username");
+
+ } else {
+
+ // start a new commissioning session
+ commissioning = true;
+ atmpt_cntr = 0;
+ tmp_usrname = j_post["username"].asString();
+ aasid = gen_aasid();
+
+ //send confirmation message
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, false, aasid, MSG_NEWPASSWD, AASTYPE_QSTN);
+ parse_cmd = "Received username: " + tmp_usrname + ", awaiting password request";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+ }
+ // we're in a commissioning session
+ } else {
+ if(tmp_pass == "") {
+ //password request
+ if(j_post["aasAnswer"].asString() == "") {
+ //password was omitted
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_NOPASS, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " password omission";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+
+ }else if (j_post["aasID"] != aasid) {
+ //aasID mismatch
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_BADAASID, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " aasid does not match";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+ } else if (j_post["username"].asString() != tmp_usrname) {
+ //user ID mismatch
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_USRMISMATCH, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " returned a mismatched username mid session";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+ } else {
+ //successful password request
+ atmpt_cntr = 0;
+ tmp_pass = j_post["aasAnswer"].asString();
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, false, aasid, MSG_RETYPEPASS, AASTYPE_QSTN);
+ parse_cmd = tmp_usrname + " successfuly requested a password. Awaiting verification";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+ }
+ } else {
+ //password request
+ if(j_post["aasAnswer"].asString() == "") {
+ //password was omitted
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_NOPASS, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " password omission";
+
+ }else if (j_post["aasID"] != aasid) {
+ //aasID mismatch
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_BADAASID, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " aasid does not match";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+
+ } else if (j_post["username"].asString() != tmp_usrname) {
+ //user ID mismatch
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_USRMISMATCH, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " returned a mismatched username mid session";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+
+ } else if (j_post["aasAnswer"].asString() != tmp_pass) {
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_PWMISMATCH, AASTYPE_ERR);
+ parse_cmd = tmp_usrname + " verification attempt did not match";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+
+ } else {
+ //successful password request
+ atmpt_cntr = 0;
+ commissioning = false;
+ std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, false, aasid, MSG_SUCCESS, AASTYPE_INFO);
+ parse_cmd = tmp_usrname + " data entry successful. Creating user account";
+ syslog (LOG_ALERT, parse_cmd.c_str());
+
+ /******
+ * COMMISSIONING CODE
+ * ***/
+
+ /****useradd****/
+ fpopen = popen(useradd_cmd_gen(tmp_usrname).c_str(), "r");
+ if (fpopen == NULL) {
+ syslog (LOG_ALERT, "popen for useradd failed to return a useful pointer");
+ //handle error via logging, closing
+ }
+
+ while (fgets(fileread, MAX_GET, fpopen) != NULL) {
+ printf("%s", fileread);
+ }
+ popen_ret = pclose(fpopen);
+ if (popen_ret == -1) {
+ syslog (LOG_ALERT, "pclose for useradd failed");
+ //handle error via logging, closing
+ }
+
+ /****passwd****/
+ fpopen = popen(passwd_cmd_gen(tmp_pass).c_str(), "w");
+ if (fpopen == NULL) {
+ syslog (LOG_ALERT, "popen for passwd failed to return a useful pointer");
+ //handle error via logging, closing
+ }
+ parse_cmd= tmp_pass + '\n' + tmp_pass;
+ fputs(parse_cmd.c_str(), fpopen);
+ popen_ret = pclose(fpopen);
+ if (popen_ret == -1) {
+ syslog (LOG_ALERT, "pclose for passwd failed");
+ // handle error via logging, closing
+ }
+
+ /****mts-ubpasswd*/
+ fpopen = popen(POPEN_MTS_UBPW.c_str(), "w");
+ if (fpopen == NULL) {
+ syslog (LOG_ALERT, "mts-uppasswd for passwd failed to return a useful pointer");
+ //handle error via logging, closing
+ }
+ fputs(tmp_pass.c_str(), fpopen);
+ popen_ret = pclose(fpopen);
+ if (popen_ret == -1) {
+ syslog (LOG_ALERT, "pclose for mts-uppasswd failed");
+ //handle error via logging, closing
+ }
+ /*****
+ * EMPTY TEMP VALS
+ * ***
+ *
+
+ /******
+ * CLOSEOUTCODE
+ * ****/
+ fpopen = popen(POPEN_CLOSEALL.c_str(), "r");
+ if (fpopen == NULL) {
+ syslog (LOG_ALERT, "closeall routine failed to return a useful pointer");
+ //handle error via logging, closing
+ }
+
+ while (fgets(fileread, MAX_GET, fpopen) != NULL) {
+ printf("%s", fileread);
+ }
+ popen_ret = pclose(fpopen);
+ if (popen_ret == -1) {
+ syslog (LOG_ALERT, "pclose for closeall routine failed ");
+ //handle error via logging, closing
+ }
+ complete = true;
+ }
+ }
+ }
+ }
+ }
+
+ // restore stdio streambufs
+ std::cin.rdbuf(cin_streambuf);
+ std::cout.rdbuf(cout_streambuf);
+ std::cerr.rdbuf(cerr_streambuf);
+
+ return 0;
+}
+
+/*****message delivery function definitions ********************************************/
+
+std::string confirmCommissioning() {
+ std::string formatted_output;
+ Json::Value output;
+ Json::StyledWriter writer;
+
+ output["code"] = 200;
+ output["status"] = "success";
+
+ formatted_output = HEADER + writer.write(output) + "\n";
+
+ return formatted_output;
+}
+
+std::string printMsg(bool &is_commission, int &atmpt_cntr, std::string &pw, bool is_err, std::string aasid, std::string msg, std::string aastype) {
+ std::string formatted_output, msg_output;
+ Json::Value output;
+ Json::StyledWriter writer;
+ bool aasDone = false;
+ int retries_left;
+
+ //set aasDone
+ if (aastype == MSG_SUCCESS) {
+ aasDone = true;
+ }
+
+ //not commissioning yet.
+ msg_output = msg;
+ if (is_err) {
+ msg_output += ": Please try again.";
+
+ //mid commission attempt.
+ if (is_commission) {
+ atmpt_cntr++;
+ retries_left = NUM_ATTEMPTS - atmpt_cntr;
+ msg_output = msg + ": Retries left: " + std::to_string(retries_left);
+
+ //commission attempts exhausted.
+ if (retries_left <= 0) {
+ atmpt_cntr = 0;
+ is_commission = false;
+ pw = "";
+ msg_output += ". Attempts exhausted. Please reattempt username.";
+ }
+ }
+ }
+
+ output["code"] = 200;
+ output["result"]["aasDone"] = aasDone;
+ output["result"]["aasID"] = aasid;
+ output["result"]["aasMsg"] = msg_output;
+ output["result"]["aasType"] = aastype;
+ output["status"] = "success";
+
+ return HEADER + writer.write(output) + "\n";
+}
+
+
+/******** recieve input function definitions *********************************/
+std::string get_request_content(const FCGX_Request &request) {
+ char * content_length_str = FCGX_GetParam("CONTENT_LENGTH", request.envp);
+ unsigned long content_length = STDIN_MAX;
+
+ if (content_length_str) {
+ content_length = strtol(content_length_str, &content_length_str, 10);
+ if (*content_length_str) {
+ std::cerr << "Can't Parse 'CONTENT_LENGTH='"
+ << FCGX_GetParam("CONTENT_LENGTH", request.envp)
+ << "'. Consuming stdin up to " << STDIN_MAX << std::endl;
+ }
+
+ if (content_length > STDIN_MAX) {
+ content_length = STDIN_MAX;
+ }
+ } else {
+ // Do not read from stdin if CONTENT_LENGTH is missing
+ content_length = 0;
+ }
+
+ char * content_buffer = new char[content_length];
+ std::cin.read(content_buffer, content_length);
+ content_length = std::cin.gcount();
+
+ // Chew up any remaining stdin - this shouldn't be necessary
+ // but is because mod_fastcgi doesn't handle it correctly.
+
+ // ignore() doesn't set the eof bit in some versions of glibc++
+ // so use gcount() instead of eof()...
+ do std::cin.ignore(1024); while (std::cin.gcount() == 1024);
+
+ std::string content(content_buffer, content_length);
+ delete [] content_buffer;
+ return content;
+}
+
+std::string gen_aasid() {
+ std::string output;
+ std::string table = "ABCDEFGHIJKLMNOPQRSTUVWZYZ1234567890";
+
+srand(time(0));
+ for (int i=0; i<AASID_LENGTH; i++) {
+ output += table[rand() % (table.length()-1)];
+ }
+ return output;
+}
+
+bool properInput(Json::Value jobj) {
+ bool aasid = false;
+ bool aasanswr = false;
+ bool usrname = false;
+ std::vector<std::string> memberNames;
+ std::string m_username = "username";
+ std::string m_aasid = "aasID";
+ std::string m_aasAnswr = "aasAnswer";
+
+ memberNames = jobj.getMemberNames();
+
+ for (int i=0; i < memberNames.size(); i++) {
+ if (memberNames[i] == m_username) {
+ usrname = true;
+ } else if (memberNames[i] == m_aasid) {
+ aasid = true;
+ } else if (memberNames[i] == m_aasAnswr) {
+ aasanswr = true;
+ }
+ }
+ if (aasid && aasanswr && usrname) {
+ return true;
+ }
+ return false;
+}
+/* TODO: expand list of legal and illegal names as needed */
+bool legalName(std::string name) {
+ /*
+ std::string cmd = "/usr/bin/id -u \"";
+ char *temp = NULL;
+ std::string popen_out;
+ */
+ if (getpwnam(name.c_str()) == NULL) {
+ syslog (LOG_ALERT, "Received a legal name request");
+ //the name is available
+ return true;
+ }
+ syslog (LOG_ALERT, "Receives an illegal name request");
+ return false;
+}
+
+/*** generate command functions ***/
+std::string passwd_cmd_gen(std::string pw) {
+ std::string cmd = "/usr/bin/passwd \"";
+ cmd +=pw;
+ cmd +="\"";
+ return cmd;
+}
+
+std::string useradd_cmd_gen(std::string usr) {
+ std::string cmd = "/usr/sbin/useradd -U -m -G sudo,dialout,disk -s /bin/bash \"";
+ cmd +=usr;
+ cmd +="\"";
+ return cmd;
+} \ No newline at end of file
diff --git a/src/commission_func.h b/src/commission_func.h
new file mode 100644
index 0000000..5f9307e
--- /dev/null
+++ b/src/commission_func.h
@@ -0,0 +1,180 @@
+/************************************************
+ *
+ *
+ *
+ *
+ * **********************************************/
+
+
+
+#ifndef __FCGICOMMISSIONING_H
+#define __FCGICOMMISSIONING_H
+
+#include "fcgio.h"
+#include <shadow.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <string.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <jsoncpp/json/json.h>
+#include <stdio.h>
+#include <iostream>
+#include <string>
+#include <ctime>
+#include <algorithm>
+
+/* Constant declarations */
+const int STDIN_MAX = 1000000;
+const int NUM_ATTEMPTS = 3;
+const int AASID_LENGTH = 30;
+const int MAX_GET = 4096;
+
+/* header declarations */
+const std::string HEADER = "Content-type:application/json\r\n\r\n";
+
+/* message declarations */
+const std::string ERR_MALDATA = "Submitted data is malformed";
+const std::string ERR_BADAASID = "aasID is incorrect or missing";
+const std::string ERR_PWMISMATCH = "Password verification failed. Password mismatch";
+const std::string ERR_USRMISMATCH = "username does not match request entry.";
+const std::string ERR_NOPASS = "No password given";
+const std::string ERR_ILLEGALUSRNAME = "Username is not permitted. Attempt new username";
+
+//TEST COOOODE
+//const std::string DEBUG ="Content-type:text/plain\r\n";
+const std::string ERROR_PARSE = "Content-type: application/json\r\n\r\n{\r\n\"error\" : \"Json input failed to parse\",\r\n\"status\" : \"failed\"\r\n}\n";
+//END TEST COOOOODE
+
+const std::string MSG_NEWPASSWD = "New password: ";
+const std::string MSG_RETYPEPASS = "Retype new password: ";
+const std::string MSG_SUCCESS = "Change password success!";
+
+/* aasType declarations */
+
+const std::string AASTYPE_QSTN = "question; input hide";
+const std::string AASTYPE_INFO = "info";
+const std::string AASTYPE_ERR = "error";
+
+/* popen cmd declarations */
+
+const std::string POPEN_CLOSEALL = "/usr/sbin/start-stop-daemon -S -p /var/run/commissionoff.pid -b -a /bin/bash -- -c /usr/libexec/commission/off.sh";
+const std::string POPEN_MTS_UBPW = "/sbin/mts-uppasswd -up";
+
+/* popen cmd generators */
+std::string passwd_cmd_gen(std::string pw);
+std::string useradd_cmd_gen(std::string usr);
+
+/* function headers */
+
+
+/******************************************************************************
+ * begin_fcgi
+ *
+ * Begin_fcgi holds the main code loop for the commissioning.fcgi binary. The
+ * basic flow is as follow:
+ *
+ * open log and initialize the fcgx request environment
+ *
+ * set the streambuffers to utilize fcgi stream buffers
+ *
+ * receive a request
+ *
+ * handle the requests.
+ *
+ * If all requests are correct, create a user
+ *
+ * end fcgi commissioning
+ *
+ * ***************************************************************************/
+int begin_fcgi();
+
+
+/* helper functions */
+
+/******************************************************************************
+ * properInput
+ *
+ * properInput receives a Json::Value and determines if its contents is a
+ * message properly formatted for fcgicommissioning input. Proper input is:
+ *
+ * username : <value of username>
+ * aasID : <value of aasID>
+ * aasAnswer : <value of aaAnswer>
+ *
+ * returns true if properly formed, false if improper
+ * ***************************************************************************/
+bool properInput(Json::Value jobj);
+
+/******************************************************************************
+ * legalName
+ *
+ * legalName checks if the requested username already exists or is implemented
+ * by the system. Returns true if the name is available, false if used or
+ * unavailable
+ * ***************************************************************************/
+bool legalName(std::string name);
+
+/******************************************************************************
+ * confirmCommissioning
+ *
+ * confirmCommissioning returns a simple, standardized confirmation message that
+ * fcgicommissioning is in fact up and running. Used when no POST data is given,
+ * but instead a GET is requested by the user.
+ * ***************************************************************************/
+std::string confirmCommissioning();
+
+
+/******************************************************************************
+ * printMsg
+ *
+ * printMsg formulates the response data fcgicommissioning returns after the
+ * user POSTs a properly formed JSON request. Values it requires are:
+ *
+ * is_commission: true if a pending commissioning attempt is occuring, false
+ * if there is no current commissioning requests yet logged.
+ *
+ * atmpt_cntr: The amount of attempts the user has left to return proper data.
+ * printMsg automatically iterates the attempts left whenever the
+ * is_err flag is set, and resets the atmpt_cntr and is_commission
+ * states when all attempts are consumed, resetting the commission
+ * attempt to the beginning.
+ *
+ * pw: the password the user has requested.
+ *
+ * is_err: true if the message being sent is an error message, false if
+ * it is a successful message
+ *
+ * aasid: the aasid the system has stored to check against user requests.
+ *
+ * errorMsg: receives a string that represents the standardized error messages
+ * to return to the user. These error messages are declared as 'const'
+ * values within commission_func.h
+ *
+ * aasType: a string that represents the aas message type the user is currently
+ * receiving. These aasType messages are declares as 'const' values
+ * within commission_func.h
+ *
+ * ***************************************************************************/
+std::string printMsg(bool &is_commission, int &atmpt_cntr, std::string &pw, bool is_err, std::string aasid, std::string errorMsg, std::string aastype);
+
+/*****************************************************************************
+ * get_request_content
+ *
+ * Parses the FCGX request sent by the user and returns its value as string data
+ * ***************************************************************************/
+std::string get_request_content(const FCGX_Request &request);
+
+
+/********************************************************************************
+ * gen_aasid
+ *
+ * generates an aasid to represent the current commissioning session. This value is
+ * stored by fcgicommissioning throughout a successful commissioning attempt to
+ * check against valid messages, and is reset when a user exhausts their commissioning
+ * attempts and needs to start over.
+ * ******************************************************************************/
+std::string gen_aasid();
+
+#endif /* ~__FCGICOMMISSIONING_H */
+
diff --git a/src/fcgicommissioning.cc b/src/fcgicommissioning.cc
new file mode 100644
index 0000000..89f106e
--- /dev/null
+++ b/src/fcgicommissioning.cc
@@ -0,0 +1,32 @@
+/******************************************************************************
+ * Commissioning.fcgi
+ *
+ * fcgi commissioning utility for first time user setup through lighttpd
+ *
+ * Copyright (C) 2020 by Multi-Tech Systems
+ *
+ * Author: Patrick Murphy <pmurphy@multitech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *****************************************************************************/
+
+#include "commission_func.h"
+
+
+int main (void) {
+ begin_fcgi();
+ return 0;
+}