/*****************************************************************************\ * Copyright (c) 2002-2003 Pelle Johansson. * * All rights reserved. * * * * This file is part of the moftpd package. Use and distribution of * * this software is governed by the terms in the file LICENCE, which * * should have come with this package. * \*****************************************************************************/ /* $moftpd: connection.c 1245 2004-12-09 12:01:59Z morth $ */ #include "system.h" #include "connection.h" #include "server.h" #include "main.h" #include "commands.h" #include "utf8fs/file.h" #include "utf8fs/memory.h" #include "accounter.h" #include "events.h" #include "confparse.h" static void *connections; extern int reloadConfig, urgData, debug; extern const char *localeDir, *localeSuffix; static int accounter_reply (int sock, void *user, int urgent) { connection_t *conn = user; int l; char buf[4097], *bp, *nbp, *str; #ifdef HAVE_LIBPAM static struct pam_conv conv = { conv_fun }; #endif char rhost[NI_MAXHOST], lhost[NI_MAXHOST], rport[NI_MAXSERV], lport[NI_MAXSERV]; struct sockaddr_storage lAddr, rAddr; if (conn->sock == -1) return 0; l = read (sock, buf, sizeof (buf) - 1); if (l <= 0) { if (l < 0 && errno == EINTR) return 0; close (conn->accSock); conn->accSock = -1; if (conn->accWait) reply (conn, "500 Accounter error."); close_data_connection (conn); return 0; } buf[l] = 0; for (bp = buf; bp; bp = nbp) { nbp = strchr (bp, '\n'); if (nbp) *nbp++ = 0; if (!bp[0]) continue; if (!strncmp (bp, "MSG ", 4)) reply_spont (conn, bp + 4, 0); else if (!strncmp (bp, "ABORT", 5)) { if (conn->working) { l = close_data_connection (conn); if (l == -1) return 0; if (bp[5] && bp[6]) reply (conn, "450 %s", bp + 6); else reply (conn, "450 Transfer aborted by administrator."); } } else if (!strncmp (bp, "DISCONNECT", 10)) { if (bp[10] && bp[11] && (str = talloc (strlen (bp + 11) + 4))) { strcpy (str, "421 "); strcat (str, bp + 11); reply_spont (conn, str, 1); } else reply_spont (conn, "421 Connection closed by administrator.", 1); } else if (!strcmp (bp, "RELOAD")) ; // Just ignore. else { switch (conn->accWait) { case acWelcome: l = sizeof(lAddr); getsockname(conn->sock, (struct sockaddr*)&lAddr, &l); if (getnameinfo ((struct sockaddr*)&lAddr, l, lhost, sizeof (lhost), lport, sizeof (lport), NI_NUMERICHOST | NI_NUMERICSERV)) { strcpy (lhost, "unknown"); strcpy (lport, "0"); } l = sizeof(rAddr); getpeername(conn->sock, (struct sockaddr*)&rAddr, &l); if (getnameinfo ((struct sockaddr*)&rAddr, l, rhost, sizeof (rhost), rport, sizeof (rport), NI_NUMERICHOST | NI_NUMERICSERV)) { strcpy (rhost, "unknown"); strcpy (rport, "0"); } l = -1; if (sscanf (bp, "ALLOW %d", &l) == 1) { #ifdef USE_SQL if (conn->server->sql.type && conn->server->sqlConnectQuery) { if (sql_connect (&conn->server->sql, conn->server->sqlHost, conn->server->sqlUser, conn->server->sqlDB, conn->server->sqlPass, conn->server->sqlCert, conn->server->sqlKey)) l = -2; else { sql_arg_t args[5]; int res; args[0].ch = 's'; args[0].str = tstring (sql_quote (conn->server->name)); args[1].ch = 'l'; args[1].str = tstring (sql_quote (lhost)); args[2].ch = 'r'; args[2].str = tstring (sql_quote (rhost)); args[3].ch = 'L'; args[3].str = lport; args[4].ch = 'R'; args[4].str = rport; res = sql_query (&conn->server->sql, conn->server->sqlConnectQuery, 5, args); if (res <= 0) l = -2; else { const char *field, *val; int b; for (res = 0; (field = sql_fetch_cell (&conn->server->sql, -1, res)); res++) { val = sql_fetch_cell (&conn->server->sql, 0, res); if (!val) continue; if (!strcasecmp (field, "ALLOW")) { b = parse_bool (val); if (!b) { l = -2; break; } } else if (!strcasecmp (field, "LANG")) { str = talloc (strlen (localeDir) + strlen (val) + strlen (localeSuffix) + 1); if (str) { strcpy (str, localeDir); strcat (str, val); strcat (str, localeSuffix); b = open_shared (str); if (b >= 0) { if (conn->langFd >= 0) close_shared (conn->langFd); conn->langFd = b; pfree (conn->currLang, conn); conn->currLang = pstring (val, conn); } } } } } sql_free_result (&conn->server->sql); if (l < 0 && !conn->server->sqlRefs) sql_disconnect (&conn->server->sql); } } #endif } if (l >= 0) { conn->id = l; #ifdef HAVE_LIBPAM if (conn->pamh) pam_end (conn->pamh, PAM_SUCCESS); conv.appdata_ptr = conn; l = pam_start (conn->server->pam_service, NULL, &conv, &conn->pamh); if (l != PAM_SUCCESS) { reply (conn, "421 %s.", pam_strerror (conn->pamh, l)); disconnected (conn); return 0; } #ifdef PAM_TTY pam_set_item (conn->pamh, PAM_TTY, conn->server->pam_service); #endif #ifdef PAM_RHOST if (strcmp (rhost, "unknown")) pam_set_item (conn->pamh, PAM_RHOST, rhost); #endif #endif if (conn->oldserver) { #ifdef USE_SQL if (!conn->oldserver->sqlRefs) sql_disconnect (&conn->oldserver->sql); #endif pfree (conn->oldserver, conn); conn->oldserver = NULL; } else { syslog (LOG_INFO, "Connection to %s from %s (id: %d)", conn->server->name, rhost, conn->id); // Start listening for commands. add_read_fd (conn->sock, control_handler, conn); } if (conn->server->welcomeMsg) reply_msg (conn, "220 %s", conn->server->welcomeMsg); else { if (find_server ((struct sockaddr*)&lAddr, (struct sockaddr*)&rAddr, conn->server)) { reply (conn, "220-Service available at %s (%s).", conn->server->name, PACKAGE_STRING); reply (conn, "220 You can use the HOST command to switch to other servers."); } else reply (conn, "220 Service available at %s (%s).", conn->server->name, PACKAGE_STRING); } } else { if (l < -1 || !strcmp (bp, "DENY")) { pfree (conn->server, conn); if (conn->oldserver) { conn->server = conn->oldserver; conn->oldserver = NULL; if (l < -1) reply (conn, "500 Access denied."); else reply (conn, "500 Connection limit reached."); break; } conn->server = pattach (find_server ((struct sockaddr*)&lAddr, (struct sockaddr*)&rAddr, conn->server), conn); if (conn->server) { if (accounter (conn->accSock, "CONNECT %s %s\n", rhost, conn->server->name)) { syslog (LOG_ERR, "Connect from %s: Error in acocunter: %m", rhost); reply (conn, "421 Error: %s. Please try again later.", strerror (errno)); disconnected (conn); return 0; } continue; } else { if (l < -1) { reply (conn, "421 No service available at this address."); syslog (LOG_NOTICE, "Connect from %s: Denied by rule.", rhost); } else { reply (conn, "421 Connection limit reached."); syslog (LOG_NOTICE, "Connect from %s: Connection limit reached.", rhost); } disconnected (conn); return 0; } } else { if (conn->oldserver) { pfree (conn->server, conn); conn->server = conn->oldserver; conn->oldserver = NULL; reply (conn, "500 Temporary error."); break; } reply (conn, "421 Temporary error. Please try again later."); syslog (LOG_ERR, "Connect from %s: Error in acocunter reply: %s", rhost, buf); disconnected (conn); return 0; } } break; case acLogin: if (!strcmp (bp, "ALLOW")) { if (authenticated (conn)) { unauthenticated (conn); reply (conn, "530 Failed to authenticate: %s (root dir inaccessible?).", strerror (errno)); break; } user_setup_environ (conn->user, 1, conn->extRFd, conn->extWFd); if (conn->user->anonymous) reply (conn, "230 Anonymous access granted."); else reply (conn, "230 Logged in as %s.", conn->user->name); } else { unauthenticated (conn); if (!strcmp (bp, "DENY")) reply (conn, "530 Login limit reached."); else reply (conn, "530 Accounter error. Please try again later."); } break; case acSending: if (!strcmp (bp, "ALLOW")) { if (open_data_connection (conn)) { reply (conn, "425 Failed to open data connection: %s.", strerror(errno)); pfree (conn->filePath, conn); conn->filePath = NULL; if (close_data_connection (conn)) return 0; } else { user_setup_environ (conn->user, conn->authed, conn->extRFd, conn->extWFd); if (conn->type == ttImage) reply (conn, "150 Opening BINARY data connection for sending of \"%s\" (%lld bytes).", print_path (conn->filePath), conn->dataLen); else reply (conn, "150 Opening ASCII data connection for sending of \"%s\".", print_path (conn->filePath)); conn->working = 1; } } else { if (!strcmp (bp, "DENY")) reply (conn, "550 Permission denied."); else reply (conn, "550 Accounter error. Please try again later."); pfree (conn->filePath, conn); conn->filePath = NULL; if (close_data_connection (conn)) return 0; } break; case acGetting: if (!strcmp (bp, "ALLOW")) { if (open_data_connection (conn)) { reply (conn, "425 Failed to open data connection: %s.", strerror (errno)); pfree (conn->filePath, conn); conn->filePath = NULL; if(close_data_connection (conn) == -1) return 0; } else { user_setup_environ (conn->user, conn->authed, conn->extRFd, conn->extWFd); reply (conn, "150 Opening %s data connection for retrieval of \"%s\".", conn->type == ttImage? "BINARY" : "ASCII", print_path (conn->filePath)); conn->working = 1; } } else { if (!strcmp (bp, "DENY\n")) reply (conn, "550 Permission denied."); else reply (conn, "550 Accounter error. Please try again later."); pfree (conn->filePath, conn); conn->filePath = NULL; if (close_data_connection (conn)) return 0; } break; case acList: if (!strcmp (bp, "END")) reply (conn, "200 End of list."); else { bp[strlen (bp) - 1] = 0; reply (conn, " %s", bp); continue; } break; case acMsg: if (!strcmp (bp, "OK")) reply (conn, "200 Message sent."); else if (!strcmp (bp, "INVALID")) reply (conn, "501 Invalid connection id."); else reply (conn, "500 Unknown error."); break; case acAbort: if (!strcmp (bp, "OK")) reply (conn, "200 Transfer aborted."); else if (!strcmp (bp, "INVALID")) reply (conn, "501 Invalid connection id."); else reply (conn, "500 Unknown error."); break; case acDisconnect: if (!strcmp (bp, "OK")) reply (conn, "200 Disconnected."); else if (!strcmp (bp, "INVALID")) reply (conn, "501 Invalid connection id."); else reply (conn, "500 Unknown error."); break; } conn->accWait = 0; } } if (!conn->accWait && !conn->sleepUntil) run_commands (conn); return 0; } ssize_t conn_plain_writer (const void *channel, const void *buf, size_t len) { return write ((int)channel, buf, len); } static ssize_t conn_plain_vecs_writer (const void *channel, struct iovec *vecs, int num) { struct msghdr msg = {0}; msg.msg_iov = vecs; msg.msg_iovlen = num; return sendmsg ((int)channel, &msg, 0); } int count_connections (void) { connection_t *conn; int i = 0; for (conn = pchild (connections, NULL); conn; conn = pchild (connections, conn)) i++; return i; } connection_t *new_connection(int sock, server_t *serv, int accSock) { connection_t *res; struct sockaddr_storage lAddr, rAddr; int lAl, rAl; char rhost[NI_MAXHOST], lhost[NI_MAXHOST], lport[NI_MAXSERV]; int i; /* Check that it really is a socket and get the local address/port. */ lAl = sizeof(lAddr); if(getsockname(sock, (struct sockaddr*)&lAddr, &lAl)) return NULL; rAl = sizeof(lAddr); if(getpeername(sock, (struct sockaddr*)&rAddr, &rAl)) return NULL; if (getnameinfo ((struct sockaddr*)&rAddr, rAl, rhost, sizeof (rhost), NULL, 0, NI_NUMERICHOST)) strcpy (rhost, "unknown"); if(!connections) { connections = proot(); if(!connections) return NULL; } res = palloc(sizeof(connection_t), connections, NULL); if(!res) return NULL; res->sock = sock; res->langFd = -1; res->id = -1; res->controlWrite = conn_plain_writer; res->controlWriteVecs = conn_plain_vecs_writer; res->controlChannel = (void*)sock; if(serv) res->server = pattach(serv, res); else { /* Find the server running on this address/port. */ res->server = pattach(find_server((struct sockaddr*)&lAddr, (struct sockaddr*)&rAddr, NULL), res); if(!res->server) { if (getnameinfo ((struct sockaddr*)&lAddr, lAl, lhost, sizeof (lhost), lport, sizeof (lport), NI_NUMERICHOST | NI_NUMERICSERV)) { strcpy (lhost, "unknown"); strcpy (lport, "unknown"); } syslog (LOG_NOTICE, "Connection from %s, but no server on %s port %s", rhost, lhost, lport); reply(res, "421 No service available at this address."); close (res->sock); pfree(res, connections); if(!errno) errno = EINVAL; return NULL; } } if (accSock >= 0) res->accSock = accSock; else res->accSock = connect_accounter (); if (res->accSock < 0) { reply (res, "421 Accounter is not available."); close (res->sock); pfree (res, connections); if (!errno) errno = EINVAL; return NULL; } accounter (res->accSock, "SET PID %d\n", (int)getpid ()); switch(lAddr.ss_family) { case AF_INET: res->activePort = ntohs (((struct sockaddr_in*)&lAddr)->sin_port) - 1; ((struct sockaddr_in*)&lAddr)->sin_port = htons (res->activePort); break; case AF_INET6: res->activePort = ntohs (((struct sockaddr_in6*)&lAddr)->sin6_port) - 1; ((struct sockaddr_in6*)&lAddr)->sin6_port = htons (res->activePort); break; } if(!res->server->allowLowPorts) { switch(rAddr.ss_family) { case AF_INET: if(ntohs (((struct sockaddr_in*)&rAddr)->sin_port) < 1024) { syslog (LOG_NOTICE, "%d: Client port < 1024", res->sock); reply(res, "421 Server does not accept client ports < 1024."); close (res->sock); pfree(res, connections); errno = EINVAL; return NULL; } break; case AF_INET6: if(ntohs (((struct sockaddr_in6*)&rAddr)->sin6_port) < 1024) { syslog (LOG_NOTICE, "%d: Client port < 1024", res->sock); reply(res, "421 Server does not accept client ports < 1024."); close (res->sock); pfree(res, connections); errno = EINVAL; return NULL; } } } res->lDataAddr = lAddr; res->rDataAddr = rAddr; res->dataSock = -1; res->fileFd = -1; res->mlstTags = tfType | tfUnique | tfModify | tfPerm | tfSize; res->extRFd = res->extWFd = -1; i = -1; setsockopt (sock, SOL_SOCKET, SO_OOBINLINE, &i, sizeof (i)); i = IPTOS_LOWDELAY; setsockopt (sock, IPPROTO_IP, IP_TOS, &i, sizeof (i)); if (accounter (res->accSock, "CONNECT %s %s\n", rhost, res->server->name)) { syslog (LOG_ERR, "%d: Error in acocunter: %m", res->sock); reply (res, "421 Error: %s. Please try again later.", strerror (errno)); disconnected (res); return NULL; } res->accWait = acWelcome; /* Only add accounter fd for now. */ add_read_fd (res->accSock, accounter_reply, res); return res; } void disconnected(connection_t *conn) { unauthenticated (conn); conn->spontQuit = 0; close_data_connection (conn); remove_read_fd(conn->sock); remove_read_fd (conn->accSock); close (conn->accSock); conn->accSock = -1; syslog (LOG_INFO, "%d: Disconnected", conn->id); #ifdef USE_TLS if (conn->tlsControl) { tls_stop (conn->tlsControl); tls_free (conn->tlsControl); } if (conn->tlsData) { tls_stop (conn->tlsData); tls_free (conn->tlsData); } if (conn->tlsClientCert) tls_free_cert (conn->tlsClientCert); #endif close(conn->sock); conn->sock = -1; if (conn->langFd >= 0) close_shared (conn->langFd); #ifdef HAVE_LIBPAM if (conn->pamh) pam_end (conn->pamh, PAM_SUCCESS); conn->pamh = NULL; #endif if (conn->inLookUp) { #ifdef USE_SQL if (!--conn->server->sqlRefs) sql_disconnect (&conn->server->sql); #endif } pfree(conn, connections); } void quit_all_connections(const char *str) { connection_t *conn; if (!connections) return; for(conn = pchild(connections, NULL); conn; conn = pchild(connections, conn)) reply_spont(conn, str, 1); } void disconnect_all_connections(void) { connection_t *conn; if (!connections) return; while((conn = pchild(connections, NULL))) disconnected(conn); } void check_idle(void) { connection_t *conn, *nconn; time_t currtime; int maxIdle; if(!connections) return; time(&currtime); for(conn = pchild(connections, NULL); conn; conn = nconn) { nconn = pchild(connections, conn); if (conn->accWait) continue; if (conn->sleepUntil && currtime >= conn->sleepUntil) { conn->sleepUntil = 0; run_commands (conn); } if(conn->user) maxIdle = conn->user->maxIdle; else maxIdle = conn->server->maxIdle; if(maxIdle && !conn->working) { if(currtime > conn->lastCommand + maxIdle * 2) disconnected(conn); else if(currtime > conn->lastCommand + maxIdle && !conn->spontQuit) reply_spont(conn, "421 Connection timed out.", 1); } } } void reply(connection_t *conn, const char *format, ...) { va_list ap; char *str; int doFree = 0, isLastLine = 1; if (conn->langFd >= 0 && format) format = scan_strings (conn->langFd, format); if (format) { va_start (ap, format); #ifdef HAVE_VASPRINTF vasprintf (&str, format, ap); doFree = 1; #else str = talloc (4097); if (str) vsnprintf (str, 4096, format, ap); #endif va_end (ap); if (!str) { str = tstring (format); doFree = 0; } if (str) isLastLine = (str[0] != ' ' && str[3] == ' '); // Change the numeric code if needed. if (str && conn->spontReply && str[0] != ' ') { str[3] = '-'; if (conn->spontQuit) strncpy (str, conn->spontReply, 3); else strncpy (conn->spontReply, str, 3); } send_telnet(conn, str, 1); } // Only send spontaneous reply after last line of a previous message. if (conn->spontReply && (!format || isLastLine)) { send_telnet(conn, conn->spontReply, 1); pfree(conn->spontReply, conn); conn->spontReply = NULL; } time(&conn->lastCommand); #if HAVE_VASPRINTF if(doFree) free(str); #endif } int reply_file (connection_t *conn, const char *format, const char *file, const char *lastfmt) { int fd = open (file, O_RDONLY); char buf[1025], *nbp; int l, bl; if (fd < 0) { syslog (LOG_ERR, "Failed to open %s: %m.", file); return -1; } bl = 0; while ((l = read (fd, buf + bl, 1024 - bl)) > 0 || bl > 0) { if (l > 0) bl += l; buf[bl] = 0; nbp = strchr (buf, '\n'); if (!nbp) break; *nbp++ = 0; if (bl < 1024 && !*nbp) // Check for file ending with newline. break; reply (conn, format, buf); if (!nbp) break; bl -= nbp - buf; memmove (buf, nbp, bl); } if (bl) { if (lastfmt) format = lastfmt; reply (conn, format, buf); } close (fd); return 0; } void reply_msg (connection_t *conn, const char *format, const char *msg) { if (msg[0] == '/') { char *fmt = tstring (format); fmt[3] = '-'; reply_file (conn, fmt, msg, format); } else reply (conn, format, msg); } void reply_spont(connection_t *conn, const char *str, int closeConn) { pfree (conn->spontReply, conn); if (conn->langFd >= 0) conn->spontReply = pstring (scan_strings (conn->langFd, str), conn); else conn->spontReply = pstring (str, conn); if (closeConn) conn->spontQuit = 1; } #define CHECK_VEC_SSP \ if (cvec >= svec - 1) \ { \ svec *= 2; \ vecs = trealloc (vecs, sizeof (struct iovec) * svec); \ if (!vecs) \ return; \ } \ if (ssp < sp) \ { \ vecs[cvec].iov_base = (void*)ssp; \ vecs[cvec++].iov_len = sp - ssp; \ } void send_telnet(connection_t *conn, const char *str, int newline) { const char *sp, *ssp; struct iovec *vecs = talloc (sizeof (struct iovec) * 4); int cvec = 0, svec = 4; syslog (LOG_DEBUG, "%d: SEND: %s", conn->id, str); for (ssp = sp = str; *sp; sp++) { switch (*(unsigned char*)sp) { case '\r': if (*++sp != '\n') { if (vecs) { CHECK_VEC_SSP; vecs[cvec].iov_base = (void*)""; vecs[cvec++].iov_len = 1; } else conn->controlWrite (conn->controlChannel, ssp, sp - ssp); ssp = sp--; } break; case 255: if (vecs) { CHECK_VEC_SSP; vecs[cvec].iov_base = (void*)"\xFF"; vecs[cvec++].iov_len = 1; } else { if (ssp < sp) conn->controlWrite (conn->controlChannel, ssp, sp - ssp); conn->controlWrite (conn->controlChannel, "\xFF", 1); } ssp = sp; break; default: break; } } if (vecs) { CHECK_VEC_SSP; if (newline) { vecs[cvec].iov_base = (void*)"\r\n"; vecs[cvec++].iov_len = 2; } conn->controlWriteVecs (conn->controlChannel, vecs, cvec); } else { if (ssp < sp) conn->controlWrite (conn->controlChannel, ssp, sp - ssp); if (newline) conn->controlWrite (conn->controlChannel, "\r\n", 2); } } int control_handler(int sock, void *user, int urgent) { connection_t *conn = user; int l; char *buffp; if (conn->sock == -1) return 0; if(urgent) { char *tmpBuf = talloc(1024); /* * Discard all urgent data after telnet parsing. */ if(tmpBuf) { l = recv(sock, tmpBuf, 1024, MSG_OOB); if(l > 0) parse_telnet(conn, tmpBuf, l); } return 0; } if(conn->readBufferLen >= sizeof(conn->readBuffer) - 1) { /* Someone sent us a line longer than we can handle. Shame on them! */ syslog (LOG_NOTICE, "%d: Input line overflow.", conn->id); conn->overFlow = 1; conn->readBufferLen = 0; } /* Append what we read. */ buffp = conn->readBuffer + conn->readBufferLen; #ifdef USE_TLS if (conn->tlsControl) { l = -1; switch (conn->tlsState) { case 0: l = tls_accept (conn->tlsControl); if (l == 1) { syslog (LOG_INFO, "%d: Successfully negotiated TLS.", conn->id); conn->tlsState = 1; conn->controlWrite = (writeFun_t)tls_write; conn->controlWriteVecs = (writeVecsFun_t)tls_write_vecs; conn->controlChannel = conn->tlsControl; if (conn->tlsClientCert) tls_free_cert (conn->tlsClientCert); conn->tlsClientCert = tls_get_peer_cert (conn->tlsControl); if ((conn->server->tlsOptions & tlsVerifyClient) && conn->server->loginTLS) { const char *clientName = tstring (tls_get_cn (conn->tlsClientCert)); if (clientName) { if (!conn->inLookUp) { conn->inLookUp = 1; #ifdef USE_SQL conn->server->sqlRefs++; #endif } conn->user = find_user (conn, clientName, 1, conn); if(conn->user) { if (conn->user->allowSecLogin) { #ifdef HAVE_LIBPAM pam_set_item (conn->pamh, PAM_USER, conn->user->name); #endif if (authenticated (conn)) { reply (conn, "421 Failed to authenticate: %s (root dir inaccessible?).", strerror (errno)); disconnected (conn); return 0; } user_setup_environ (conn->user, 1, conn->extRFd, conn->extWFd); return 0; } pfree (conn->user, conn); conn->user = NULL; } syslog (LOG_NOTICE, "%d: No user for CN \"%s\".", conn->id, clientName); } else syslog (LOG_NOTICE, "%d: No CN in certificate.", conn->id); if (conn->server->tlsNoLogout) { reply (conn, "421 Failed to find a user for your certificate."); disconnected (conn); } } } else if (l) { if (l == -1) reply (conn, "421 TLS negotiation failed."); syslog (LOG_NOTICE, "%d: Error in TLS negotiation: %s.", conn->id, tls_error (conn->tlsControl, l)); disconnected (conn); } return 0; case 1: l = tls_read (conn->tlsControl, buffp, sizeof (conn->readBuffer) - conn->readBufferLen); break; case 2: l = tls_stop (conn->tlsControl); if (l == 1) { tls_free (conn->tlsControl); conn->tlsControl = NULL; } else if (l) { syslog (LOG_NOTICE, "%d: Error in TLS shutdown: %s.", conn->id, tls_error (conn->tlsControl, l)); disconnected (conn); } return 0; } } else #endif l = read (sock, buffp, sizeof (conn->readBuffer) - conn->readBufferLen); if(l < 0) { /* Error */ #ifdef USE_TLS if (conn->tlsControl) { syslog (LOG_DEBUG, "%d: TLS read error: %s.", conn->id, tls_error (conn->tlsControl, l)); disconnected (conn); } else #endif if(errno != EINTR) { syslog (LOG_DEBUG, "%d: control_handler: read: %m", conn->id); disconnected (conn); } return 0; } if(!l) { syslog (LOG_DEBUG, "%d: Client suddenly disconnected.", conn->id); /* EOF == they disconnected. */ disconnected(conn); return 0; } /* Parse telnet stuff. */ conn->readBufferLen += parse_telnet(conn, buffp, l); if (!conn->sleepUntil) // Will be run by check_idle otherwise. run_commands (conn); return 0; } void run_commands (connection_t *conn) { char *buffp; /* Check for line breaks. */ while((buffp = memchr(conn->readBuffer, 0, conn->readBufferLen))) { if(conn->overFlow) { /* We can't handle this. Beginning of line is already discarded. */ conn->overFlow = 0; reply(conn, "500 Line too long."); } else { if (debug) { if (!strncasecmp (conn->readBuffer, "PASS ", 5)) syslog (LOG_DEBUG, "%d: RECV: PASS (hidden)", conn->id); else syslog (LOG_DEBUG, "%d: RECV: %s", conn->id, conn->readBuffer); } if(handle_command(conn, conn->readBuffer)) return; } /* Discard line */ conn->readBufferLen -= ++buffp - conn->readBuffer; memmove(conn->readBuffer, buffp, conn->readBufferLen); } } int parse_telnet(connection_t *conn, char *buff, int len) { unsigned char *curr, *end = (unsigned char*)buff + len; unsigned char retBuf[3]; /* * Telnet command handling. Answer all DO with WON'T, and all WILL with DON'T * for now. Remove all null characters and put nulls at linebreaks. */ for(curr = (unsigned char*)buff; curr < end;) { switch(conn->telnetMode) { case NUL: switch(*curr) { case NUL: if(conn->telnetFlags & tfCR) { *curr = '\r'; if(conn->telnetFlags & tfSubOpt) { #if 0 if(conn->suboptLen < 100) conn->subopt[conn->suboptLen++] = *curr; #endif memmove(curr, curr + 1, --end - curr); } else curr++; } else memmove(curr, curr + 1, --end - curr); conn->telnetFlags &= ~tfCR; break; case '\n': if(conn->telnetFlags & tfCR) *curr = 0; conn->telnetFlags &= ~tfCR; if(conn->telnetFlags & tfSubOpt) { #if 0 if(conn->suboptLen < 100) conn->subopt[conn->suboptLen++] = *curr; #endif memmove(curr, curr + 1, --end - curr); } else curr++; break; case '\r': conn->telnetFlags |= tfCR; memmove(curr, curr + 1, --end - curr); /* * CR will be ignored unless followed by either NL or NUL, but that * what the telnet rfc says a CR must be followed by so. */ break; case IAC: conn->telnetFlags &= ~tfCR; conn->telnetMode = IAC; memmove(curr, curr + 1, --end - curr); break; default: conn->telnetFlags &= ~tfCR; if (conn->telnetFlags & tfSubOpt) { #if 0 if (conn->suboptLen < 100) conn->subopt[conn->suboptLen++] = *curr; #endif memmove (curr, curr + 1, --end - curr); } else curr++; break; } break; case IAC: switch(*curr) { case IAC: // Quoted character 255 if(conn->telnetFlags & tfSubOpt) { #if 0 if(conn->suboptLen < 100) conn->subopt[conn->suboptLen++] = *curr; #endif memmove(curr, curr + 1, --end - curr); } else curr++; conn->telnetMode = NUL; break; case SB: memmove(curr, curr + 1, --end - curr); conn->telnetMode = SB; break; case SE: conn->telnetMode = SE; memmove(curr, curr + 1, --end - curr); break; case WILL: case WONT: case DO: case DONT: conn->telnetMode = *curr; memmove(curr, curr + 1, --end - curr); break; default: if(*curr >= 240) memmove(curr, curr + 1, --end - curr); else curr++; conn->telnetMode = NUL; break; } break; case SB: syslog (LOG_NOTICE, "%d: Unnegotiated suboption.", conn->id); conn->telnetMode = NUL; if (conn->telnetFlags & tfSubOpt) syslog (LOG_NOTICE, "%d: Nested suboption.", conn->id); conn->telnetFlags |= tfSubOpt; conn->sb = *curr; memmove (curr, curr + 1, --end - curr); break; case SE: conn->telnetMode = NUL; if (!(conn->telnetFlags & tfSubOpt)) syslog (LOG_NOTICE, "%d: Suboption end without suboption start.", conn->id); else if(*curr == conn->sb) { conn->telnetFlags &= ~tfSubOpt; #if 0 conn->suboptLen = 0; #endif } else syslog (LOG_NOTICE, "%d: Wrong suboption ended, got %d expected %d.", conn->id, *curr, conn->sb); memmove (curr, curr + 1, --end - curr); break; case WILL: case DO: retBuf[0] = IAC; retBuf[1] = (conn->telnetMode == WILL? DONT : WONT); retBuf[2] = *curr; write(conn->sock, retBuf, 3); conn->telnetMode = NUL; memmove (curr, curr + 1, --end - curr); break; case WONT: case DONT: conn->telnetMode = NUL; memmove (curr, curr + 1, --end - curr); break; } } return (char*)end - buff; } int authenticated(connection_t *conn) { conn->authed = 1; if (conn->inLookUp) { conn->inLookUp = 0; #ifdef USE_SQL if (!--conn->server->sqlRefs) sql_disconnect (&conn->server->sql); #endif } if (conn->extRFd >= 0) { remove_read_fd (conn->extRFd); close (conn->extRFd); conn->extRFd = -1; } if (conn->extWFd >= 0) { close (conn->extWFd); conn->extWFd = -1; } if(conn->user) { if (conn->email) syslog (LOG_INFO, "%d: User %s (email \"%s\") logged in.", conn->id, conn->user->name, conn->email); else if (conn->account) syslog (LOG_INFO, "%d: User %s (account \"%s\") logged in.", conn->id, conn->user->name, conn->account); else syslog (LOG_INFO, "%d: User %s logged in.", conn->id, conn->user->name); if (conn->user->access_program) { int ipipe[2], opipe[2], i; const char *args[4], **ap = args; const char *envs[] = { NULL }; const char *prog = conn->user->access_program; conn->mlstTags &= ~tfPerm; // perm might be heavy if checked externally. if (pipe (ipipe) || pipe (opipe)) conn->extRFd = conn->extWFd = -1; else { *ap++ = prog; *ap++ = conn->user->name; if (conn->account) *ap++ = conn->account; *ap = NULL; switch (vfork ()) { case -1: // Error close (ipipe[0]); close (ipipe[1]); close (opipe[0]); close (opipe[1]); conn->extRFd = conn->extWFd = -1; break; case 0: // Child if (dup2 (ipipe[1], 1) == -1) _exit (1); if (dup2 (opipe[0], 0) == -1) _exit (1); for (i = 2; i < getdtablesize (); i++) close (i); execve (prog, (char**)args, (char**)envs); _exit (1); default: close (ipipe[1]); close (opipe[0]); conn->extRFd = ipipe[0]; conn->extWFd = opipe[1]; break; } } } if(conn->user->chroot) { const char *new_root; super_privs (0); new_root = set_root (conn->user->chroot); if (!new_root) { syslog (LOG_ERR, "%d: Failed to chroot: %m.", conn->id); return -1; } if (strcmp (new_root, conn->user->chroot)) { pfree (conn->user->chroot, conn->user); conn->user->chroot = pstring (new_root, conn->user); } } if(conn->user->home) conn->cwd = pstring (set_cwd ("~", conn->user->home), conn); } else syslog (LOG_NOTICE, "%d: Unknown user logged in.", conn->id); #ifdef HAVE_LIBPAM super_privs (0); // TODO: Check return values. pam_setcred (conn->pamh, PAM_ESTABLISH_CRED); pam_open_session (conn->pamh, PAM_SILENT); #endif return 0; } void unauthenticated(connection_t *conn) { #ifdef HAVE_LIBPAM super_privs (0); // TODO: Check return values. if (conn->pamh) { pam_close_session (conn->pamh, PAM_SILENT); pam_setcred (conn->pamh, PAM_DELETE_CRED); } #endif conn->authed = 0; pfree(conn->user, conn); conn->user = NULL; pfree(conn->email, conn); conn->email = NULL; pfree (conn->account, conn); conn->account = NULL; if (conn->extRFd >= 0) { remove_read_fd (conn->extRFd); close (conn->extRFd); conn->extRFd = -1; } if (conn->extWFd >= 0) { close (conn->extWFd); conn->extWFd = -1; } }