/**************************************************************************** * This file is part of the project AqHome. * AqHome (c) by 2025 Martin Preuss, all rights reserved. * * The license for this file can be found in the file COPYING which you * should have received along with this file. ****************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include "./mroot_p.h" #include "aqhome-cgi/modules/devices/mdevices.h" #include "aqhome-cgi/modules/common/madmin.h" #include #include #include #include /* ------------------------------------------------------------------------------------------------ * defs and enums * ------------------------------------------------------------------------------------------------ */ #define AQH_SERVICE_SESSION_MAXAGE 86400 /* ------------------------------------------------------------------------------------------------ * global vars * ------------------------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------------------------ * forward declarations * ------------------------------------------------------------------------------------------------ */ static AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName); static int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sLastPathElem); static int _handleRqIndex(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf); static int _handleRqLogin(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf); static int _handleRqLoginGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf); static int _handleRqLoginPost(AQCGI_MODULE *m, AQCGI_REQUEST *rq); static AQCGI_USER *_getAndCheckUser(AQCGI_MODULE *m, AQCGI_REQUEST *rq); /* ------------------------------------------------------------------------------------------------ * code * ------------------------------------------------------------------------------------------------ */ AQCGI_MODULE *AQH_ModRoot_new(AQCGI_SERVICE *sv, const char *baseFolder) { AQCGI_MODULE *m; m=AQCGI_Module_new(); AQH_ModService_Extend(m, sv, baseFolder); AQH_ModService_SetHandleRequestFn(m, _handleRequest); AQH_ModService_SetLoadSubModuleFn(m, _loadSubModule); return m; } AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, GWEN_UNUSED AQCGI_REQUEST *rq, GWEN_UNUSED AQCGI_SESSION *session, const char *sModuleName) { AQCGI_SERVICE *sv; sv=AQH_ModService_GetService(m); if (strcasecmp(sModuleName, "devices")==0) { AQCGI_MODULE *mSub; mSub=AQCGI_Service_LoadModule(sv, sModuleName); if (mSub) { const char *s; GWEN_BUFFER *nbuf; nbuf=GWEN_Buffer_new(0, 256, 0, 1); s=AQH_ModService_GetBaseFolder(m); GWEN_Buffer_AppendArgs(nbuf, "%s/devices", s?s:"."); AQH_ModDevices_Extend(mSub, AQH_ModService_GetService(m), GWEN_Buffer_GetStart(nbuf)); AQCGI_Module_Tree2_AddChild(m, mSub); GWEN_Buffer_free(nbuf); return mSub; } } else if (strcasecmp(sModuleName, "admin")==0) { AQCGI_MODULE *mSub; mSub=AQCGI_Service_LoadModule(sv, sModuleName); if (mSub) { const char *s; GWEN_BUFFER *nbuf; nbuf=GWEN_Buffer_new(0, 256, 0, 1); s=AQH_ModService_GetBaseFolder(m); GWEN_Buffer_AppendArgs(nbuf, "%s/admin", s?s:"."); AQH_ModAdmin_Extend(mSub, AQH_ModService_GetService(m), GWEN_Buffer_GetStart(nbuf)); AQCGI_Module_Tree2_AddChild(m, mSub); GWEN_Buffer_free(nbuf); return mSub; } } return NULL; } int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_UNUSED AQCGI_SESSION *session, const char *sLastPathElem) { GWEN_BUFFER *dbuf; dbuf=GWEN_Buffer_new(0, 256, 0, 1); AQH_ModService_AddHeader(m, "en", dbuf); if (strcasecmp(sLastPathElem, "login")==0) _handleRqLogin(m, rq, dbuf); else if (strcasecmp(sLastPathElem, "signup")==0) { AQCGI_Request_SetResponseCode(rq, 501); AQCGI_Request_SetResponseText(rq, "Not Implemented"); } else if (strcasecmp(sLastPathElem, "confirm")==0) { AQCGI_Request_SetResponseCode(rq, 501); AQCGI_Request_SetResponseText(rq, "Not Implemented"); } else if (strcasecmp(sLastPathElem, "index.html")==0) _handleRqIndex(m, rq, dbuf); else { AQCGI_Request_SetResponseCode(rq, 404); AQCGI_Request_SetResponseText(rq, "Not Found"); } AQH_ModService_AddFooter(m, "en", dbuf); AQCGI_Request_SetBufferResponseBody(rq, dbuf); AQCGI_Request_AddResponseHeaderData(rq, "Content-type: text/html"); return AQCGI_SendResponse(rq); } int _handleRqIndex(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf) { if (AQCGI_Request_GetRequestMethod(rq)==AQCGI_REQUEST_METHOD_GET) return AQH_ModService_RespondWithFile(m, rq, "en", "index.html", dbuf); else { DBG_ERROR(NULL, "Invalid request method %d", AQCGI_Request_GetRequestMethod(rq)); AQCGI_Request_SetResponseCode(rq, 405); AQCGI_Request_SetResponseText(rq, "Method Not Allowed"); return GWEN_ERROR_GENERIC; } } int _handleRqLogin(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf) { if (AQCGI_Request_GetRequestMethod(rq)==AQCGI_REQUEST_METHOD_GET) _handleRqLoginGet(m, rq, dbuf); else if (AQCGI_Request_GetRequestMethod(rq)==AQCGI_REQUEST_METHOD_POST) _handleRqLoginPost(m, rq); else { DBG_ERROR(NULL, "Invalid request method %d", AQCGI_Request_GetRequestMethod(rq)); AQCGI_Request_SetResponseCode(rq, 405); AQCGI_Request_SetResponseText(rq, "Method Not Allowed"); return GWEN_ERROR_GENERIC; } return 0; } int _handleRqLoginGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf) { GWEN_DB_NODE *dbQuery; const char *sSrc; GWEN_DB_NODE *dbVars; int rv; dbVars=GWEN_DB_Group_new("vars"); dbQuery=AQCGI_Request_GetDbQuery(rq); sSrc=dbQuery?GWEN_DB_GetCharValue(dbQuery, "src", 0, NULL):NULL; if (sSrc && *sSrc) GWEN_DB_SetCharValue(dbVars, 0, "destination", sSrc); rv=AQH_ModService_RespondWithVarFile(m, rq, "en", "login.html", dbVars, dbuf); GWEN_DB_Group_free(dbVars); return rv; } int _handleRqLoginPost(AQCGI_MODULE *m, AQCGI_REQUEST *rq) { AQCGI_SERVICE *sv; AQCGI_USER *user; AQCGI_SESSION *session; GWEN_BUFFER *tbuf; GWEN_TIMESTAMP *ts; GWEN_DB_NODE *dbPost; const char *s; int rv; DBG_ERROR(NULL, "Handling request"); sv=AQH_ModService_GetService(m); user=_getAndCheckUser(m, rq); if (user==NULL) { DBG_INFO(NULL, "here"); return GWEN_ERROR_GENERIC; } ts=GWEN_Timestamp_NowInLocalTime(); AQCGI_User_SetTimestampLastLogin(user, ts); rv=AQCGI_Service_SaveUser(sv, user); if (rv<0) { DBG_ERROR(NULL, "Error saving user \"%s\"", AQCGI_User_GetAlias(user)); AQCGI_Request_SetResponseCode(rq, 500); AQCGI_Request_SetResponseText(rq, "Internal Error"); AQCGI_User_free(user); return rv; } /* generate session */ tbuf=GWEN_Buffer_new(0, 64, 0, 1); AQCGI_GenerateSessionId(tbuf); session=AQCGI_Session_new(); AQCGI_Session_SetTimestampCreation(session, ts); AQCGI_Session_SetTimestampLastAccess(session, ts); AQCGI_Session_SetUid(session, GWEN_Buffer_GetStart(tbuf)); GWEN_Buffer_free(tbuf); AQCGI_Session_SetUserAlias(session, AQCGI_User_GetAlias(user)); rv=AQCGI_Service_AddSession(sv, session); if (rv<0) { DBG_ERROR(NULL, "Error adding session for user \"%s\" (%d)", AQCGI_User_GetAlias(user), rv); AQCGI_Request_SetResponseCode(rq, 500); AQCGI_Request_SetResponseText(rq, "Internal Error"); AQCGI_Session_free(session); AQCGI_User_free(user); return GWEN_ERROR_INTERNAL; } /* add Set-Cookie header */ tbuf=GWEN_Buffer_new(0, 256, 0, 1); GWEN_Buffer_AppendArgs(tbuf, "Set-Cookie: session=%s; max-age=%d", AQCGI_Session_GetUid(session), AQH_SERVICE_SESSION_MAXAGE); AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(tbuf)); GWEN_Buffer_free(tbuf); /* finish */ dbPost=AQCGI_Request_GetDbPostBody(rq); s=dbPost?GWEN_DB_GetCharValue(dbPost, "dest", 0, NULL):NULL; if (s && *s) { GWEN_BUFFER *ubuf; ubuf=GWEN_Buffer_new(0, 128, 0, 1); if (GWEN_Text_UnescapeToBufferTolerant(s, ubuf)>=0) { const char *p; p=GWEN_Buffer_GetStart(ubuf); if (*p=='/') AQH_ModService_Redirect(rq, p); else { DBG_ERROR(NULL, "Destination does not start with \"/\", not using it [%s]", p); AQCGI_Request_AddResponseHeaderData(rq, "Location: index.html"); AQCGI_Request_SetResponseCode(rq, 303); AQCGI_Request_SetResponseText(rq, "See other"); } } GWEN_Buffer_free(ubuf); } else { AQCGI_Request_AddResponseHeaderData(rq, "Location: index.html"); AQCGI_Request_SetResponseCode(rq, 303); AQCGI_Request_SetResponseText(rq, "See other"); } AQCGI_Session_free(session); AQCGI_User_free(user); return 0; } AQCGI_USER *_getAndCheckUser(AQCGI_MODULE *m, AQCGI_REQUEST *rq) { GWEN_DB_NODE *dbPost; dbPost=AQCGI_Request_GetDbPostBody(rq); if (dbPost) { AQCGI_SERVICE *sv; const char *sUserName; const char *sPasswd; AQCGI_USER *user; const char *hashedPaswd; GWEN_BUFFER *buf; sv=AQH_ModService_GetService(m); sUserName=GWEN_DB_GetCharValue(dbPost, "userid", 0, NULL); sPasswd=GWEN_DB_GetCharValue(dbPost, "password", 0, NULL); if (!(sUserName && *sUserName && sPasswd && *sPasswd)) { DBG_ERROR(NULL, "Either user name or password missing"); AQCGI_Request_SetResponseCode(rq, 400); AQCGI_Request_SetResponseText(rq, "Bad Request"); return NULL; } DBG_ERROR(NULL, "Loading user \"%s\" (%p)", sUserName, sv); user=AQCGI_Service_LoadUser(sv, sUserName); if (user==NULL) { DBG_ERROR(NULL, "User \"%s\" not found", sUserName); AQCGI_Request_SetResponseCode(rq, 403); AQCGI_Request_SetResponseText(rq, "Forbidden"); return NULL; } DBG_ERROR(NULL, "Loaded user \"%s\"", sUserName); if (AQCGI_User_GetState(user)!=AQCGI_UserState_Active) { DBG_ERROR(NULL, "User \"%s\" not active", sUserName); AQCGI_Request_SetResponseCode(rq, 403); AQCGI_Request_SetResponseText(rq, "Forbidden"); AQCGI_User_free(user); return NULL; } hashedPaswd=AQCGI_User_GetHashedPassword(user); if (!(hashedPaswd && *hashedPaswd)) { DBG_ERROR(NULL, "User \"%s\" has no hashed password", sUserName); AQCGI_Request_SetResponseCode(rq, 403); AQCGI_Request_SetResponseText(rq, "Forbidden"); AQCGI_User_free(user); return NULL; } buf=GWEN_Buffer_new(0, 256, 0, 1); AQCGI_HashMd256ToBuffer(sPasswd, buf); DBG_ERROR(NULL, "Password: [%s]", sPasswd); // TODO: Remove!!! DBG_ERROR(NULL, "Hashed password: [%s]", GWEN_Buffer_GetStart(buf)); if (strcasecmp(GWEN_Buffer_GetStart(buf), hashedPaswd)!=0) { DBG_ERROR(NULL, "Bad password for user \"%s\"", sUserName); AQCGI_Request_SetResponseCode(rq, 403); AQCGI_Request_SetResponseText(rq, "Forbidden"); GWEN_Buffer_free(buf); AQCGI_User_free(user); return NULL; } GWEN_Buffer_free(buf); DBG_ERROR(NULL, "User \"%s\" accepted", sUserName); return user; } else { DBG_ERROR(NULL, "No POST data"); AQCGI_SendResponseWithStatus(rq, 400, "Bad Request"); AQCGI_Request_SetResponseCode(rq, 400); AQCGI_Request_SetResponseText(rq, "Bad Request"); return NULL; } }