/*****************************************************************************\ * Copyright (c) 2002 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: server.c 1264 2005-04-06 13:32:27Z morth $ */ #include "system.h" #include "server.h" #include "main.h" #include "connection.h" #include "utf8fs/memory.h" #include "defaults.h" #include "accounter.h" #include "events.h" static void *servers; static int *serverSockets, numServerSockets; extern int forkConnections, debug, accClientSock, selfFailedFork; extern int fakeChroot, doFakeChroot; static void free_server (void *ptr) { server_t *serv = ptr; #ifdef USE_TLS if (serv->tlsCert) tls_free_cert (serv->tlsCert); if (serv->tlsKey) tls_free_key (serv->tlsKey); #endif #ifdef USE_SQL sql_disconnect (&serv->sql); #endif } server_t *new_server(const char *name, server_t *parent) { server_t *res; if(!servers) { servers = proot(); if(!servers) return NULL; } res = palloc(sizeof(server_t), servers, free_server); if(!res) return NULL; res->name = pstring(name, res); if(!res->name) { pfree(res, servers); return NULL; } if(parent) { res->numPorts = parent->numPorts; if(res->numPorts) { res->ports = palloc(sizeof(int) * res->numPorts, res, NULL); if(!res->ports) { pfree(res, servers); return NULL; } memcpy(res->ports, parent->ports, sizeof(int) * res->numPorts); } res->passIfInvalid = parent->passIfInvalid; res->allowForeign = parent->allowForeign; res->allowOutOfRange = parent->allowOutOfRange; res->allowLowPorts = parent->allowLowPorts; res->minPasvPort = parent->minPasvPort; res->maxPasvPort = parent->maxPasvPort; res->users = pattach(parent->users, res); res->access = pattach(parent->access, res); res->defHardLink = parent->defHardLink; res->chroot = pattach(parent->chroot, res); res->allowLogin = parent->allowLogin; res->passwordNeeded = parent->passwordNeeded; res->loginFailedMsg = pattach(parent->loginFailedMsg, res); res->userInvalidMsg = pattach(parent->userInvalidMsg, res); res->passRequestMsg = pattach(parent->passRequestMsg, res); res->anonPassMsg = pattach(parent->anonPassMsg, res); res->welcomeMsg = pattach(parent->welcomeMsg, res); res->dirMsgFile = pattach(parent->dirMsgFile, res); res->maxIdle = parent->maxIdle; res->sleepOnFail = parent->sleepOnFail; res->maxLoginAttempts = parent->maxLoginAttempts; res->allowSecLogin = parent->allowSecLogin; res->numAliases = parent->numAliases; res->maxConnects = parent->maxConnects; if(res->numAliases) { res->aliases = palloc(sizeof(*res->aliases) * res->numAliases, res, NULL); if(!res->aliases) { pfree(res, servers); return NULL; } memcpy(res->aliases, parent->aliases, sizeof(*res->aliases) * res->numAliases); } #ifdef HAVE_LIBPAM res->pam_service = pattach (parent->pam_service, res); #endif #ifdef USE_SQL res->sql.type = parent->sql.type; res->sqlHost = pattach (parent->sqlHost, res); res->sqlUser = pattach (parent->sqlUser, res); res->sqlDB = pattach (parent->sqlDB, res); res->sqlPass = pattach (parent->sqlPass, res); res->sqlCert = pattach (parent->sqlCert, res); res->sqlKey = pattach (parent->sqlKey, res); res->sqlUserQuery = pattach (parent->sqlUserQuery, res); res->sqlDirQuery = pattach (parent->sqlDirQuery, res); res->sqlConnectQuery = pattach (parent->sqlConnectQuery, res); #endif } else { res->allowForeign = defAllowForeign; res->allowOutOfRange = defAllowOutOfRange; res->allowLowPorts = defAllowLowPorts; res->allowUnbound = defAllowUnbound; res->minPasvPort = defMinPasvPort; res->maxPasvPort = defMaxPasvPort; res->passIfInvalid = defPassIfInvalid; res->allowLogin = defAllowLogin; res->allowSecLogin = defAllowSecLogin; res->passwordNeeded = defPasswordNeeded; res->maxIdle = defMaxIdle; res->sleepOnFail = defSleepOnFail; res->maxLoginAttempts = defMaxLoginAttempts; res->tlsOptions = defTLSOptions; #ifdef HAVE_LIBPAM res->pam_service = pstring (PACKAGE_NAME, res); #endif } return res; } void quit_all_servers (void) { pfree (servers, NULL); servers = NULL; close_server_sockets (); } void close_server_sockets (void) { int i; for (i = 0; i < numServerSockets; i++) { remove_read_fd (serverSockets[i]); close (serverSockets[i]); } pfree (serverSockets, NULL); serverSockets = NULL; numServerSockets = 0; } void add_server_port(server_t *serv, int port) { if(serv->numPorts++) serv->ports = prealloc(serv->ports, serv->numPorts * sizeof(*serv->ports)); else serv->ports = palloc(sizeof(*serv->ports), serv, NULL); serv->ports[serv->numPorts - 1] = port; } void add_server_binding(server_t *serv, const char *mask) { if(serv->numBindings++) serv->bindings = prealloc(serv->bindings, serv->numBindings * sizeof(*serv->bindings)); else serv->bindings = palloc(sizeof(*serv->bindings), serv, NULL); serv->bindings[serv->numBindings - 1] = pstring (mask, serv->bindings); } void add_server_range (server_t *serv, struct sockaddr *addr, struct sockaddr *mask) { int sz, i; char *ap, *ep; if (serv->numRanges++) serv->ranges = prealloc (serv->ranges, serv->numRanges * sizeof (*serv->ranges)); else serv->ranges = palloc (sizeof (*serv->ranges), serv, NULL); serv->ranges[serv->numRanges - 1].addr = *(struct sockaddr_storage*)addr; serv->ranges[serv->numRanges - 1].mask = *(struct sockaddr_storage*)mask; switch (mask->sa_family) { case AF_INET: ap = (char*)&((struct sockaddr_in*)mask)->sin_addr; ep = ap + 4; break; case AF_INET6: ap = (char*)&((struct sockaddr_in6*)mask)->sin6_addr; ep = ap + 16; break; default: ap = ep = NULL; break; } for (sz = 0; ap < ep; ap++) { for (i = 0; i < 8; i++) { if (*ap & (1 << i)) sz++; } } serv->ranges[serv->numRanges - 1].maskSize = sz; } void server_add_alias(server_t *serv, const char *alias, const char *user) { int i; for(i = 0; i < serv->numAliases; i++) { if(!strcmp(serv->aliases[i].alias, alias)) { pfree(serv->aliases[i].user, serv->aliases); serv->aliases[i].user = pstring(user, serv->aliases); return; } } if(serv->numAliases++) serv->aliases = prealloc(serv->aliases, serv->numAliases * sizeof(*serv->aliases)); else serv->aliases = palloc(sizeof(*serv->aliases), serv, NULL); serv->aliases[serv->numAliases - 1].alias = pstring(alias, serv->aliases); serv->aliases[serv->numAliases - 1].user = pstring(user, serv->aliases); } const char *server_expand_alias(const server_t *serv, const char *name) { int i; for(i = 0; i < serv->numAliases; i++) { if(!strcmp(serv->aliases[i].alias, name)) return serv->aliases[i].user; } return name; } static void *visited_servers; server_t *find_server (const struct sockaddr *addr, const struct sockaddr *raddr, const server_t *last) { char host[NI_MAXHOST]; int port; server_t *res; struct addrinfo hints = {}, *aires, *aicurr; int i, addrlen; switch(addr->sa_family) { case AF_INET: port = ntohs (((struct sockaddr_in*)addr)->sin_port); addrlen = sizeof (struct sockaddr_in); break; case AF_INET6: port = ntohs (((struct sockaddr_in6*)addr)->sin6_port); addrlen = sizeof (struct sockaddr_in6); break; default: return NULL; } visited_servers = proot (); /* First look for numeric host to avoid dns lookup timeout. */ if (!getnameinfo (addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST)) { res = find_named_server (host, port, raddr, last); if (res == (server_t*)-1) last = NULL; else if (res) { pfree (visited_servers, NULL); visited_servers = NULL; return res; } } /* Next lookup hostname. */ if (!getnameinfo (addr, addrlen, host, sizeof(host), NULL, 0, NI_NAMEREQD)) { res = find_named_server (host, port, raddr, last); if (res == (server_t*)-1) last = NULL; else if (res) { pfree (visited_servers, NULL); visited_servers = NULL; return res; } } /* Next lookup configured server names. */ hints.ai_socktype = SOCK_STREAM; for(res = pchild(servers, NULL); res; res = pchild(servers, res)) { if (plinked (res, visited_servers)) continue; for(i = 0; i < res->numPorts; i++) if(res->ports[i] == port) break; if(i == res->numPorts) continue; if(strchr(res->name, '*') || strchr(res->name, '?')) continue; aires = NULL; if(!getaddrinfo(res->name, NULL, &hints, &aires) && aires) { for(aicurr = aires; aicurr; aicurr = aicurr->ai_next) { if(same_addr(addr, aicurr->ai_addr, 0)) break; } freeaddrinfo(aires); if(aicurr) { if (last) { if (res == last) last = NULL; pattach (res, visited_servers); } else { pfree (visited_servers, NULL); visited_servers = NULL; return res; } } } } /* Next lookup configured servers bindings. */ for(res = pchild(servers, NULL); res; res = pchild(servers, res)) { if (plinked (res, visited_servers)) continue; for(i = 0; i < res->numPorts; i++) if(res->ports[i] == port) break; if(i == res->numPorts) continue; for(i = 0; i < res->numBindings; i++) { if(strchr(res->bindings[i], '*') || strchr(res->bindings[i], '?')) continue; aires = NULL; if(!getaddrinfo(res->bindings[i], NULL, &hints, &aires) && aires) { for(aicurr = aires; aicurr; aicurr = aicurr->ai_next) { if(same_addr(addr, aicurr->ai_addr, 0)) break; } freeaddrinfo(aires); if(aicurr) { if (last) { if (res == last) last = NULL; pattach (res, visited_servers); } else { pfree (visited_servers, NULL); visited_servers = NULL; return res; } } } } } /* Last find first server on port that allows nonbound. */ for(res = pchild(servers, NULL); res; res = pchild(servers, res)) { if (plinked (res, visited_servers)) continue; if(!res->allowUnbound) continue; for(i = 0; i < res->numPorts; i++) if(res->ports[i] == port) break; if(i < res->numPorts) { if (last) { if (res == last) last = NULL; pattach (res, visited_servers); } else { pfree (visited_servers, NULL); visited_servers = NULL; return res; } } } /* No server found. */ pfree (visited_servers, NULL); visited_servers = NULL; return NULL; } server_t *find_named_server (const char *host, int port, const struct sockaddr *raddr, const server_t *last) { server_t *res, *best = NULL; int i, bestMask = -1; int wl = (last != NULL); /* First check versus names. */ for(res = pchild(servers, NULL); res; res = pchild(servers, res)) { if (visited_servers && plinked (res, visited_servers)) continue; if (port) { for(i = 0; i < res->numPorts; i++) if(res->ports[i] == port) break; if(i == res->numPorts) continue; } if (strcasecmp(host, res->name)) continue; if (!raddr) return res; if (res->numRanges) { for (i = 0; i < res->numRanges; i++) { if (check_range (raddr, (struct sockaddr*)&res->ranges[i].addr, (struct sockaddr*)&res->ranges[i].mask)) break; } if (i == res->numRanges) continue; i = res->ranges[i].maskSize; } else i = 0; if (i <= bestMask) continue; if (last) { if (res == last) last = NULL; if (visited_servers) pattach (res, visited_servers); } else { best = res; bestMask = i; } break; // Only one server with a specific name. } if (!port) return best; /* Then check versus bindings. */ for(res = pchild(servers, NULL); res; res = pchild(servers, res)) { if (visited_servers && plinked (res, visited_servers)) continue; for(i = 0; i < res->numPorts; i++) if(res->ports[i] == port) break; if(i == res->numPorts) continue; for(i = 0; i < res->numBindings; i++) { if(match_pattern(res->bindings[i], host)) break; } if (i == res->numBindings) continue; if (!raddr) return res; if (res->numRanges) { for (i = 0; i < res->numRanges; i++) { if (check_range (raddr, (struct sockaddr*)&res->ranges[i].addr, (struct sockaddr*)&res->ranges[i].mask)) break; } if (i == res->numRanges) continue; i = res->ranges[i].maskSize; } else i = 0; if (i <= bestMask) continue; if (last) { if (res == last) last = NULL; if (visited_servers) pattach (res, visited_servers); } else { best = res; bestMask = i; } } if (best) return best; if (wl && !last) return (void*)-1; return NULL; } int create_server_sockets(void) { server_t *serv; int *allPorts = NULL; int numAllPorts = 0, maxAllPorts = 0; int isAll, i, j, sock; struct sockaddr_in addr4 = {}; struct sockaddr_in6 addr6 = {}; char *binding, portBuf[8]; struct addrinfo hints = { AI_PASSIVE, 0, SOCK_STREAM }, *res, *curr; int maxSocks = 1; if (numServerSockets) maxSocks = numServerSockets; for(serv = pchild(servers, NULL); serv; serv = pchild(servers, serv)) { isAll = 0; if(serv->allowUnbound) isAll = 1; else for(i = 0; i < serv->numBindings; i++) { if(strchr(serv->bindings[i], '*') || strchr(serv->bindings[i], '?')) { /* Wildcards, have to listen on all. */ isAll = 1; break; } } if (isAll) { for (i = 0; i < serv->numPorts; i++) { for (j = 0; j < numAllPorts; j++) { if (allPorts[j] == serv->ports[i]) break; } if (j < numAllPorts) continue; if (numAllPorts >= maxAllPorts) { if (maxAllPorts) { maxAllPorts *= 2; allPorts = trealloc (allPorts, maxAllPorts * sizeof (*allPorts)); } else { maxAllPorts = 10; /* Start high, only temporary. */ allPorts = talloc (maxAllPorts * sizeof (*allPorts)); } if (!allPorts) { syslog (LOG_ERR, "talloc: %m"); return -1; } } allPorts[numAllPorts++] = serv->ports[i]; } } else { for (i = 0; i < serv->numPorts; i++) { sprintf (portBuf, "%d", serv->ports[i]); for (j = -1; j < serv->numBindings; j++) { if (j == -1) { binding = serv->name; if (strchr (binding, '*') || strchr (binding, '?')) continue; } else binding = serv->bindings[j]; if ((sock = getaddrinfo (binding, portBuf, &hints, &res)) || !res) { syslog (LOG_DEBUG, "getaddrinfo (%s, %s): %s", binding, portBuf, gai_strerror (sock)); continue; } for (curr = res; curr; curr = curr->ai_next) { sock = socket (curr->ai_family, curr->ai_socktype, curr->ai_protocol); if (sock < 0) syslog (LOG_DEBUG, "socket (%d): %m", curr->ai_family); else { isAll = -1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &isAll, sizeof(isAll)); #ifdef IPV6_V6ONLY // Only IPv6 please. This will fail on IPv4 naturally. setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &isAll, sizeof (isAll)); #endif if (bind (sock, curr->ai_addr, curr->ai_addrlen)) { syslog (LOG_DEBUG, "bind (%s, %s): %m", binding, portBuf); close (sock); } else if (listen (sock, 128)) { syslog (LOG_ERR, "listen (%s, %s): %m", binding, portBuf); close (sock); } else { // Used serv as user before, but might get wrong one then. add_read_fd (sock, connection_accepter, NULL); if (!serverSockets) { serverSockets = palloc (sizeof (int), NULL, NULL); maxSocks = 1; numServerSockets = 0; } else if (numServerSockets == maxSocks) { maxSocks *= 2; serverSockets = prealloc (serverSockets, sizeof (int) * maxSocks); } if (serverSockets) serverSockets[numServerSockets++] = sock; } } } freeaddrinfo (res); } } } } for (i = 0; i < numAllPorts; i++) { sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(sock < 0) syslog (LOG_DEBUG, "socket (PF_INET): %m"); else { j = -1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j)); addr4.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr4.sin_len = sizeof(addr4); #endif addr4.sin_port = htons (allPorts[i]); if(bind(sock, (struct sockaddr*)&addr4, sizeof(addr4))) { syslog (LOG_ERR, "bind (PF_INET, %d): %m", allPorts[i]); close(sock); } else if(listen(sock, 128)) { syslog (LOG_ERR, "listen (PF_INET, %d): %m", allPorts[i]); close(sock); } else { add_read_fd(sock, connection_accepter, NULL); if (!serverSockets) { serverSockets = palloc (sizeof (int), NULL, NULL); maxSocks = 1; numServerSockets = 0; } else if (numServerSockets == maxSocks) { maxSocks *= 2; serverSockets = prealloc (serverSockets, sizeof (int) * maxSocks); } if (serverSockets) serverSockets[numServerSockets++] = sock; } } sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); if(sock < 0) syslog (LOG_DEBUG, "socket (PF_INET6): %m"); else { j = -1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j)); #ifdef IPV6_V6ONLY // Only IPv6 please. setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &j, sizeof (j)); #endif addr6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr6.sin6_len = sizeof(addr6); #endif addr6.sin6_port = htons (allPorts[i]); if(bind(sock, (struct sockaddr*)&addr6, sizeof(addr6))) { syslog (LOG_ERR, "bind (PF_INET6, %d): %m", allPorts[i]); close(sock); } else if(listen(sock, 128)) { syslog (LOG_ERR, "listen (PF_INET6, %d): %m", allPorts[i]); close(sock); } else { add_read_fd(sock, connection_accepter, NULL); if (!serverSockets) { serverSockets = palloc (sizeof (int), NULL, NULL); maxSocks = 1; numServerSockets = 0; } else if (numServerSockets == maxSocks) { maxSocks *= 2; serverSockets = prealloc (serverSockets, sizeof (int) * maxSocks); } if (serverSockets) serverSockets[numServerSockets++] = sock; } } } return 0; } int connection_accepter(int sock, void *user, int urgent) { int csock = accept(sock, NULL, NULL); if(csock >= 0) { if (forkConnections) { int accSock = connect_accounter (); int accCSock = connect_accounter (); const char *err; switch(fork()) { case -1: // Error if (selfFailedFork) { syslog (LOG_WARNING, "fork: %m"); new_connection (csock, user, accSock); } else { err = strerror (errno); syslog (LOG_ERR, "fork: %m"); write (csock, "421 ", 4); write (csock, err, strlen (err)); write (csock, "\r\n", 2); close (csock); close (accSock); } close (accCSock); return 0; case 0: // Child break; default: // Parent close(csock); close (accSock); close (accCSock); return 0; } fakeChroot = doFakeChroot; close_server_sockets (); close_accounter (1); closelog (); openlog (getprogname (), LOG_PID | LOG_NDELAY | (debug? LOG_PERROR : 0), LOG_FTP); events_init (); if (accClientSock != -1) close (accClientSock); accClientSock = accCSock; if (accClientSock >= 0) { add_read_fd (accClientSock, accounter_master_reply, NULL); accounter (accClientSock, "SET PID %d\n", (int)getpid ()); } new_connection (csock, user, accSock); quit_all_servers (); return 1; } else new_connection (csock, user, -1); } else syslog (LOG_ERR, "accept: %m"); return 0; }