diff options
Diffstat (limited to 'src/authenticate.cc')
-rw-r--r-- | src/authenticate.cc | 358 |
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; +} |