From f8ba84f1366cc4df61b8f16afd8ed872db8930cc Mon Sep 17 00:00:00 2001 From: Patrick Murphy Date: Mon, 13 Apr 2020 13:24:41 -0500 Subject: fcgi commissioning 1.0 --- AUTHORS | 2 + ChangeLog | 0 Makefile | 8 + NEWS | 0 etc/lighttpd-commission.conf | 14 +- src/Makefile | 24 +++ src/commission_func.cc | 395 +++++++++++++++++++++++++++++++++++++++++++ src/commission_func.h | 180 ++++++++++++++++++++ src/fcgicommissioning.cc | 32 ++++ 9 files changed, 653 insertions(+), 2 deletions(-) create mode 100644 AUTHORS create mode 100644 ChangeLog create mode 100644 NEWS create mode 100644 src/Makefile create mode 100644 src/commission_func.cc create mode 100644 src/commission_func.h create mode 100644 src/fcgicommissioning.cc diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..293e0cc --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +John Klug +Patrick Murphy diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index e69de29..3df9813 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,8 @@ +all: + (cd src; make) + +install: + (cd src; make install) + +clean: + (cd src; make clean) \ No newline at end of file diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/etc/lighttpd-commission.conf b/etc/lighttpd-commission.conf index d6ecb32..f267860 100644 --- a/etc/lighttpd-commission.conf +++ b/etc/lighttpd-commission.conf @@ -22,6 +22,7 @@ server.modules = ( # "mod_status", # "mod_setenv", "mod_fastcgi", + "mod_openssl", # "mod_proxy", # "mod_simple_vhost", # "mod_evhost", @@ -213,8 +214,17 @@ fastcgi.server = ( ".php" => "socket" => "/var/run/php-fpm.socket", "broken-scriptfilename" => "enable" ) - ) - ) + ), + "/api/commissioning" => + ( "localhost" => + ( + "socket" => "/var/run/php-fpm.socket", + "bin-path" => "/usr/bin/commissioning.fcgi", + "check-local" => "disable", + "max-procs" => 1 + ) + ) + ) #fastcgi.server = ( "/api/" => # ( "localhost" => 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 : + * aasID : + * aasAnswer : + * + * 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 + * + * 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; +} -- cgit v1.2.3