summaryrefslogtreecommitdiff
path: root/src/authenticate.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/authenticate.cc')
-rw-r--r--src/authenticate.cc358
1 files changed, 358 insertions, 0 deletions
diff --git a/src/authenticate.cc b/src/authenticate.cc
new file mode 100644
index 0000000..a448238
--- /dev/null
+++ b/src/authenticate.cc
@@ -0,0 +1,358 @@
+/** --------------------------------------------------------------------
+ * @file authenticate.cc
+ * @brief Implementation of the common (C/R) authentication mechanism.
+ * @author Andreas Aardal Hanssen, Erwin Hoffmann
+ * @date 2002-2005, 2023
+ * ----------------------------------------------------------------- **/
+#include <string>
+#include <vector>
+
+#include <sys/types.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+
+// #ifndef HAVE_SYS_WAIT_H
+// #include <wait.h>
+//#else
+#include <sys/wait.h>
+//#endif
+
+#include "authenticate.h"
+#include "iodevice.h"
+#include "iofactory.h"
+#include "session.h"
+#include "convert.h"
+#include "globals.h"
+
+using namespace ::std;
+using namespace Binc;
+
+// 0 = ok
+// 1 = internal error
+// 2 = failed
+// 3 = timeout
+// -1 = abort
+//------------------------------------------------------------------------
+int Binc::authenticate(Depot &depot, const string &username,
+ const string &password, const string &challenge)
+{
+ Session &session = Session::getInstance();
+ session.setUserID(username);
+
+ // check if checkpassword is present
+ if (::access(session.unparsedArgs[0], X_OK) != 0) { // x is enough
+ bincError << "unable to start authenticator " << session.unparsedArgs[0]
+ << ": " << strerror(errno) << endl;
+ return 1;
+ }
+
+ // The information supplied on descriptor 3 is a login name
+ // terminated by \0, a password terminated by \0, a timestamp
+ // terminated by \0, and possibly more data. There are no other
+ // restrictions on the form of the login name, password, and
+ // timestamp.
+ int authintercom[2];
+ int intercomw[2];
+ int intercomr[2];
+ bool authenticated = false;
+
+ if (pipe(authintercom) == -1) {
+ session.setLastError("An error occurred when creating pipes: "
+ + string(strerror(errno)));
+ return -1;
+ }
+
+ if (pipe(intercomw) == -1) {
+ session.setLastError("An error occurred when creating pipes: "
+ + string(strerror(errno)));
+ close(authintercom[0]);
+ close(authintercom[1]);
+ return -1;
+ }
+
+ if (pipe(intercomr) == -1) {
+ session.setLastError("An error occurred when creating pipes: "
+ + string(strerror(errno)));
+ close(intercomw[0]);
+ close(intercomr[0]);
+ close(authintercom[0]);
+ close(authintercom[1]);
+ return -1;
+ }
+
+ string timestamp;
+ time_t t = time(0);
+ char *c;
+ if ((c = ctime(&t)) != 0) {
+ timestamp = c;
+ trim(timestamp);
+ } else
+ timestamp = "unknown timestamp";
+
+ string pid = to_string(session.getPid());
+
+ // execute authentication module
+ int result;
+ int childspid = fork();
+ if (childspid == -1) {
+ bincLog << "bincimap-up: pid " << pid
+ << " failed to start main server: "
+ << strerror(errno) << endl;
+ return 1;
+ }
+
+ if (childspid == 0) {
+ close(authintercom[1]);
+ close(intercomr[0]);
+ close(intercomw[1]);
+
+ if (dup2(intercomr[1], 1) == -1) {
+ bincDebug << "bincimap-up: pid " << pid
+ << " authenticate(), [auth module] dup2 failed: "
+ << strerror(errno) << endl;
+ bincDebug.flush();
+ exit(111);
+ }
+
+ if (dup2(intercomw[0], 0) == -1) {
+ bincDebug << "bincimap-up: pid " << pid
+ << " authenticate(), [auth module] dup2 failed: "
+ << strerror(errno) << endl;
+ bincDebug.flush();
+ exit(111);
+ }
+
+ if (dup2(authintercom[0], 3) == -1) {
+ bincDebug << "bincimap-up: pid " << pid
+ << " authenticate(), [auth module] dup2 failed: "
+ << strerror(errno) << endl;
+ bincDebug.flush();
+ exit(111);
+ }
+
+ if (session.unparsedArgs[0] != 0) {
+ execvp(session.unparsedArgs[0], &session.unparsedArgs[0]);
+ bincDebug << "bincimap-up: pid " << pid
+ << " authenticate(), [auth module] invocation of "
+ << session.unparsedArgs[0]
+ << " failed: " << strerror(errno) << endl;
+ bincDebug.flush();
+ exit(111);
+ }
+
+ bincLog << "bincimap-up: pid " << pid
+ << " missing mandatory -- in argument list,"
+ " after bincimap-up + arguments, before authenticator."
+ " Please check your run scripts and the man page bincimap(1) for"
+ " more on how to invoke Binc IMAP." << endl;
+ bincDebug.flush();
+ exit(111);
+ }
+
+ close(authintercom[0]);
+
+ // create the string of data to be passed to the checkpassword stub
+ int dataSize = username.length() + password.length() + challenge.length() + timestamp.length();
+ dataSize += 4;
+ char *checkpasswordData = new char[dataSize];
+ char *cpTmp = checkpasswordData;
+ strcpy(cpTmp, username.c_str());
+ cpTmp += username.length();
+ *cpTmp++ = '\0';
+ strcpy(cpTmp, password.c_str());
+ cpTmp += password.length();
+ *cpTmp++ = '\0';
+ // add challenge
+ strcpy(cpTmp, challenge.c_str());
+ cpTmp += challenge.length();
+ *cpTmp++ = '\0';
+ strcpy(cpTmp, timestamp.c_str());
+ cpTmp += timestamp.length();
+ *cpTmp++ = '\0';
+
+ bincDebug << "bincimap-up: pid " << pid
+ << " authenticate(), writing username/password to "
+ << session.unparsedArgs[0] << endl;
+
+ // write the userid
+ signal(SIGPIPE, SIG_IGN);
+ int res = write(authintercom[1], checkpasswordData, dataSize);
+ delete[] checkpasswordData;
+ if (res != dataSize) {
+ bincWarning << "bincimap-up: pid " << pid
+ << " error writing to authenticator "
+ << session.unparsedArgs[0] << ": "
+ << strerror(errno) << endl;
+ return 1;
+ }
+
+ // close the write channel. this is necessary for the checkpassword
+ // module to see an EOF.
+ close(authintercom[1]);
+ close(intercomr[1]);
+ close(intercomw[0]);
+
+ fd_set rmask;
+ FD_ZERO(&rmask);
+ FD_SET(fileno(stdin), &rmask);
+ FD_SET(intercomr[0], &rmask);
+
+ int maxfd = intercomr[0];
+ bool disconnected = false;
+ bool timedout = false;
+ bincClient.clearFlags(IODevice::HasInputLimit);
+
+ bool eof = false;
+ while (!eof) {
+ fd_set rtmp = rmask;
+ struct timeval timeout;
+
+ // time out 5 minutes after the idle timeout. we expect the main
+ // server to time out at the right time, but will shut down at
+ // T+5m in case of a server lockup.
+ timeout.tv_sec = IDLE_TIMEOUT + AUTH_PENALTY * AUTH_TIMEOUT;
+ timeout.tv_usec = 0;
+
+ // select sometimes returns when we attach to the process with
+ // tracing tools such as ktrace and strace, setting errno to
+ // EINTR.
+ int n;
+ do {
+ n = select(maxfd + 1, &rtmp, 0, 0, &timeout);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0) {
+ bincWarning << "bincimpa-up: pid " << pid
+ << " error: invalid exit from select, "
+ << strerror(errno) << endl;
+ break;
+ }
+
+ if (n == 0) {
+ bincLog << "bincimap-up: pid " << pid
+ << " server timed out after "
+ << IDLE_TIMEOUT << " seconds" << endl;
+ timedout = true;
+ break;
+ }
+
+ if (FD_ISSET(fileno(stdin), &rtmp)) {
+ authenticated = true;
+
+ do {
+ string data;
+ int ret = bincClient.readStr(&data);
+ if (ret == 0 || ret == -1) {
+ session.setLastError("client disconnected");
+ eof = true;
+ disconnected = true;
+ break;
+ }
+
+ // Fall through. Triggered when there was no data
+ // to read, even though no error has occurred
+ if (ret == -2) continue;
+
+ int w;
+ do {
+ w = write(intercomw[1], data.c_str(), data.length());
+ } while (w < 0 && errno == EINTR);
+
+ if (w > 0) Session::getInstance().addReadBytes(w);
+
+ if (w < 0) {
+ bincDebug << "bincimap-up: pid " << pid
+ << " error writing to server: "
+ << strerror(errno) << endl;
+ eof = true;
+ }
+ } while (bincClient.canRead());
+ }
+
+ if (FD_ISSET(intercomr[0], &rtmp)) {
+ char buf[8192];
+ int ret = read(intercomr[0], buf, sizeof(buf));
+ if (ret == 0) {
+ // Main server has shut down
+ eof = true;
+ break;
+ } else if (ret == -1) {
+ bincDebug << "bincimap-up: pid " << pid
+ << " error reading from server: "
+ << strerror(errno) << endl;
+ eof = true;
+ break;
+ } else {
+ // umask(0);
+ Session::getInstance().addWriteBytes(ret);
+
+ bincClient << string(buf, ret);
+ bincClient.flush();
+ }
+ }
+ }
+
+ close(intercomr[0]);
+ close(intercomw[1]);
+
+ // catch the dead baby
+ if (waitpid(childspid, &result, 0) != childspid) {
+ bincLog << "bincimap-up: pid " << pid
+ << " <" << username << "> authentication failed: "
+ << (authenticated ? "server " : session.unparsedArgs[0])
+ << " waitpid returned unexpected value" << endl;
+ string tmp = strerror(errno);
+
+ return -1;
+ }
+
+ // if the server died because we closed the sockets after a timeout,
+ // exit 3.
+ if (timedout) return 3;
+
+ if (disconnected) return 0;
+
+ if (WIFSIGNALED(result)) {
+ bincLog << "bincimap-up: pid " << pid
+ << " <" << username << "> authentication failed: "
+ << (authenticated ? "server" : session.unparsedArgs[0])
+ << " died by signal " << WTERMSIG(result) << endl;
+ sleep(AUTH_PENALTY);
+ session.setState(Session::LOGOUT);
+ return -1;
+ }
+
+ bincDebug << "bincimap-up: pid " << pid
+ << " authenticate() ,"
+ << (authenticated ? "authenticator" : "server")
+ << " exited with code " << WEXITSTATUS(result) << endl;
+
+ switch (WEXITSTATUS(result)) {
+ case 0: break;
+ case 1:
+ // authentication failed - sleep
+ bincLog << "bincimap-up: pid " << pid
+ << " <" << username << "> failed to log in" << endl;
+ sleep(AUTH_PENALTY);
+ return 2;
+ case 2: case 111: // wrong call or missing auth data
+ // abused
+ bincLog << "bincimap-up: pid " << pid
+ << " <" << username << "> authentication failed: "
+ << (authenticated ? "authenticator" : "server")
+ << " reports wrong usage" << endl;
+ return -1;
+ default:
+ // internal error -- or authenticator fooled us
+ bincLog << "bincimap-up: pid " << pid
+ << " <" << username << "> authentication failed: "
+ << (authenticated ? "authenticator" : "server")
+ << " returned " << WEXITSTATUS(result) << endl;
+ return -1;
+ }
+
+ return 0;
+}