/* the Music Player Daemon (MPD) * (c)2003-2006 by Warren Dukes (warren.dukes@gmail.com) * This project's homepage is: http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface.h" #include "command.h" #include "conf.h" #include "list.h" #include "log.h" #include "listen.h" #include "playlist.h" #include "permission.h" #include "sig_handlers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GREETING "OK MPD" #define INTERFACE_MAX_BUFFER_LENGTH (40960) #define INTERFACE_LIST_MODE_BEGIN "command_list_begin" #define INTERFACE_LIST_OK_MODE_BEGIN "command_list_ok_begin" #define INTERFACE_LIST_MODE_END "command_list_end" #define INTERFACE_DEFAULT_OUT_BUFFER_SIZE (4096) #define INTERFACE_TIMEOUT_DEFAULT (60) #define INTERFACE_MAX_CONNECTIONS_DEFAULT (10) #define INTERFACE_MAX_COMMAND_LIST_DEFAULT (2048*1024) #define INTERFACE_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) /* set this to zero to indicate we have no possible interfaces */ static int interface_max_connections = 0; /*INTERFACE_MAX_CONNECTIONS_DEFAULT; */ static int interface_timeout = INTERFACE_TIMEOUT_DEFAULT; static size_t interface_max_command_list_size = INTERFACE_MAX_COMMAND_LIST_DEFAULT; static size_t interface_max_output_buffer_size = INTERFACE_MAX_OUTPUT_BUFFER_SIZE_DEFAULT; typedef struct _Interface { char buffer[INTERFACE_MAX_BUFFER_LENGTH]; int bufferLength; int bufferPos; int fd; /* file descriptor */ FILE *fp; /* file pointer */ int open; /* open/used */ int permission; time_t lastTime; List *commandList; /* for when in list mode */ int commandListOK; /* print OK after each command execution */ size_t commandListSize; /* mem commandList consumes */ List *bufferList; /* for output if client is slow */ size_t outputBufferSize; /* mem bufferList consumes */ int expired; /* set whether this interface should be closed on next check of old interfaces */ int num; /* interface number */ char *outBuffer; int outBuflen; int outBufSize; } Interface; static Interface *interfaces = NULL; static void flushInterfaceBuffer(Interface * interface); static void printInterfaceOutBuffer(Interface * interface); static void openInterface(Interface * interface, int fd) { int flags; assert(interface->open == 0); interface->bufferLength = 0; interface->bufferPos = 0; interface->fd = fd; /* fcntl(interface->fd,F_SETOWN,(int)getpid()); */ while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ; flags |= O_NONBLOCK; while (fcntl(interface->fd, F_SETFL, flags) < 0 && errno == EINTR) ; while ((interface->fp = fdopen(fd, "rw")) == NULL && errno == EINTR) ; interface->open = 1; interface->lastTime = time(NULL); interface->commandList = NULL; interface->bufferList = NULL; interface->expired = 0; interface->outputBufferSize = 0; interface->outBuflen = 0; interface->permission = getDefaultPermissions(); interface->outBufSize = INTERFACE_DEFAULT_OUT_BUFFER_SIZE; #ifdef SO_SNDBUF { int getSize; unsigned int sockOptLen = sizeof(int); if (getsockopt(interface->fd, SOL_SOCKET, SO_SNDBUF, (char *)&getSize, &sockOptLen) < 0) { DEBUG("problem getting sockets send buffer size\n"); } else if (getSize <= 0) { DEBUG("sockets send buffer size is not positive\n"); } else interface->outBufSize = getSize; } #endif interface->outBuffer = malloc(interface->outBufSize); myfprintf(interface->fp, "%s %s\n", GREETING, VERSION); printInterfaceOutBuffer(interface); } static void closeInterface(Interface * interface) { if (!interface->open) return; interface->open = 0; while (fclose(interface->fp) && errno == EINTR) ; if (interface->commandList) freeList(interface->commandList); if (interface->bufferList) freeList(interface->bufferList); free(interface->outBuffer); SECURE("interface %i: closed\n", interface->num); } void openAInterface(int fd, struct sockaddr *addr) { int i; for (i = 0; i < interface_max_connections && interfaces[i].open; i++) ; if (i == interface_max_connections) { ERROR("Max Connections Reached!\n"); while (close(fd) && errno == EINTR) ; } else { SECURE("interface %i: opened from ", i); switch (addr->sa_family) { case AF_INET: { char *host = inet_ntoa(((struct sockaddr_in *) addr)->sin_addr); if (host) { SECURE("%s\n", host); } else { SECURE("error getting ipv4 address\n"); } } break; #ifdef HAVE_IPV6 case AF_INET6: { char host[INET6_ADDRSTRLEN + 1]; memset(host, 0, INET6_ADDRSTRLEN + 1); if (inet_ntop(AF_INET6, (void *) &(((struct sockaddr_in6 *)addr)-> sin6_addr), host, INET6_ADDRSTRLEN)) { SECURE("%s\n", host); } else { SECURE("error getting ipv6 address\n"); } } break; #endif case AF_UNIX: SECURE("local connection\n"); break; default: SECURE("unknown\n"); } openInterface(&(interfaces[i]), fd); } } static int processLineOfInput(Interface * interface) { int ret = 1; char *line = interface->buffer + interface->bufferPos; if (interface->bufferLength - interface->bufferPos > 1) { if (interface->buffer[interface->bufferLength - 2] == '\r') { interface->buffer[interface->bufferLength - 2] = '\0'; } } if (interface->commandList) { if (strcmp(line, INTERFACE_LIST_MODE_END) == 0) { DEBUG("interface %i: process command " "list\n", interface->num); ret = processListOfCommands(interface->fp, &(interface->permission), &(interface->expired), interface->commandListOK, interface->commandList); DEBUG("interface %i: process command " "list returned %i\n", interface->num, ret); if (ret == 0) commandSuccess(interface->fp); else if (ret == COMMAND_RETURN_CLOSE || interface->expired) { closeInterface(interface); } printInterfaceOutBuffer(interface); freeList(interface->commandList); interface->commandList = NULL; } else { interface->commandListSize += sizeof(ListNode); interface->commandListSize += strlen(line) + 1; if (interface->commandListSize > interface_max_command_list_size) { ERROR("interface %i: command " "list size (%lli) is " "larger than the max " "(%lli)\n", interface->num, (long long)interface-> commandListSize, (long long) interface_max_command_list_size); closeInterface(interface); ret = COMMAND_RETURN_CLOSE; } else { insertInListWithoutKey(interface->commandList, strdup(line)); } } } else { if (strcmp(line, INTERFACE_LIST_MODE_BEGIN) == 0) { interface->commandList = makeList(free, 1); interface->commandListSize = sizeof(List); interface->commandListOK = 0; ret = 1; } else if (strcmp(line, INTERFACE_LIST_OK_MODE_BEGIN) == 0) { interface->commandList = makeList(free, 1); interface->commandListSize = sizeof(List); interface->commandListOK = 1; ret = 1; } else { DEBUG("interface %i: process command \"%s\"\n", interface->num, line); ret = processCommand(interface->fp, &(interface->permission), line); DEBUG("interface %i: command returned %i\n", interface->num, ret); if (ret == 0) commandSuccess(interface->fp); else if (ret == COMMAND_RETURN_CLOSE || interface->expired) { closeInterface(interface); } printInterfaceOutBuffer(interface); } } return ret; } static int processBytesRead(Interface * interface, int bytesRead) { int ret = 0; while (bytesRead > 0) { interface->bufferLength++; bytesRead--; if (interface->buffer[interface->bufferLength - 1] == '\n') { interface->buffer[interface->bufferLength - 1] = '\0'; ret = processLineOfInput(interface); interface->bufferPos = interface->bufferLength; } if (interface->bufferLength == INTERFACE_MAX_BUFFER_LENGTH) { if (interface->bufferPos == 0) { ERROR("interface %i: buffer overflow\n", interface->num); closeInterface(interface); return 1; } interface->bufferLength -= interface->bufferPos; memmove(interface->buffer, interface->buffer + interface->bufferPos, interface->bufferLength); interface->bufferPos = 0; } if (ret == COMMAND_RETURN_KILL || ret == COMMAND_RETURN_CLOSE) { return ret; } } return ret; } static int interfaceReadInput(Interface * interface) { int bytesRead; bytesRead = read(interface->fd, interface->buffer + interface->bufferLength, INTERFACE_MAX_BUFFER_LENGTH - interface->bufferLength); if (bytesRead > 0) return processBytesRead(interface, bytesRead); else if (bytesRead == 0 || (bytesRead < 0 && errno != EINTR)) { closeInterface(interface); } else return 0; return 1; } static void addInterfacesReadyToReadAndListenSocketToFdSet(fd_set * fds, int *fdmax) { int i; FD_ZERO(fds); addListenSocketsToFdSet(fds, fdmax); for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open && !interfaces[i].expired && !interfaces[i].bufferList) { FD_SET(interfaces[i].fd, fds); if (*fdmax < interfaces[i].fd) *fdmax = interfaces[i].fd; } } } static void addInterfacesForBufferFlushToFdSet(fd_set * fds, int *fdmax) { int i; FD_ZERO(fds); for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open && !interfaces[i].expired && interfaces[i].bufferList) { FD_SET(interfaces[i].fd, fds); if (*fdmax < interfaces[i].fd) *fdmax = interfaces[i].fd; } } } static void closeNextErroredInterface(void) { fd_set fds; struct timeval tv; int i; tv.tv_sec = 0; tv.tv_usec = 0; for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open) { FD_ZERO(&fds); FD_SET(interfaces[i].fd, &fds); if (select(FD_SETSIZE, &fds, NULL, NULL, &tv) < 0) { closeInterface(&interfaces[i]); return; } } } } int doIOForInterfaces(void) { fd_set rfds; fd_set wfds; struct timeval tv; int i; int selret; int fdmax; tv.tv_sec = 1; tv.tv_usec = 0; while (1) { fdmax = 0; addInterfacesReadyToReadAndListenSocketToFdSet(&rfds, &fdmax); addInterfacesForBufferFlushToFdSet(&wfds, &fdmax); selret = select(fdmax + 1, &rfds, &wfds, NULL, &tv); if (selret == 0 || (selret < 0 && errno == EINTR)) break; if (selret < 0) { closeNextErroredInterface(); continue; } getConnections(&rfds); for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open && FD_ISSET(interfaces[i].fd, &rfds)) { if (COMMAND_RETURN_KILL == interfaceReadInput(&(interfaces[i]))) { return COMMAND_RETURN_KILL; } interfaces[i].lastTime = time(NULL); } if (interfaces[i].open && FD_ISSET(interfaces[i].fd, &wfds)) { flushInterfaceBuffer(&interfaces[i]); interfaces[i].lastTime = time(NULL); } } tv.tv_sec = 0; tv.tv_usec = 0; } return 1; } void initInterfaces(void) { int i; char *test; ConfigParam *param; param = getConfigParam(CONF_CONN_TIMEOUT); if (param) { interface_timeout = strtol(param->value, &test, 10); if (*test != '\0' || interface_timeout <= 0) { ERROR("connection timeout \"%s\" is not a positive " "integer, line %i\n", CONF_CONN_TIMEOUT, param->line); exit(EXIT_FAILURE); } } param = getConfigParam(CONF_MAX_CONN); if (param) { interface_max_connections = strtol(param->value, &test, 10); if (*test != '\0' || interface_max_connections <= 0) { ERROR("max connections \"%s\" is not a positive integer" ", line %i\n", param->value, param->line); exit(EXIT_FAILURE); } } else interface_max_connections = INTERFACE_MAX_CONNECTIONS_DEFAULT; param = getConfigParam(CONF_MAX_COMMAND_LIST_SIZE); if (param) { interface_max_command_list_size = strtoll(param->value, &test, 10); if (*test != '\0' || interface_max_command_list_size <= 0) { ERROR("max command list size \"%s\" is not a positive " "integer, line %i\n", param->value, param->line); exit(EXIT_FAILURE); } interface_max_command_list_size *= 1024; } param = getConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE); if (param) { interface_max_output_buffer_size = strtoll(param->value, &test, 10); if (*test != '\0' || interface_max_output_buffer_size <= 0) { ERROR("max output buffer size \"%s\" is not a positive " "integer, line %i\n", param->value, param->line); exit(EXIT_FAILURE); } interface_max_output_buffer_size *= 1024; } interfaces = malloc(sizeof(Interface) * interface_max_connections); for (i = 0; i < interface_max_connections; i++) { interfaces[i].open = 0; interfaces[i].num = i; } } static void closeAllInterfaces(void) { int i; fflush(NULL); for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open) { closeInterface(&(interfaces[i])); } } } void freeAllInterfaces(void) { closeAllInterfaces(); free(interfaces); interface_max_connections = 0; } void closeOldInterfaces(void) { int i; for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open) { if (interfaces[i].expired) { DEBUG("interface %i: expired\n", i); closeInterface(&(interfaces[i])); } else if (time(NULL) - interfaces[i].lastTime > interface_timeout) { DEBUG("interface %i: timeout\n", i); closeInterface(&(interfaces[i])); } } } } static void flushInterfaceBuffer(Interface * interface) { ListNode *node = NULL; char *str; int ret = 0; while ((node = interface->bufferList->firstNode)) { str = (char *)node->data; if ((ret = write(interface->fd, str, strlen(str))) < 0) break; else if (ret < strlen(str)) { interface->outputBufferSize -= ret; str = strdup(&str[ret]); free(node->data); node->data = str; } else { interface->outputBufferSize -= strlen(str) + 1; interface->outputBufferSize -= sizeof(ListNode); deleteNodeFromList(interface->bufferList, node); } interface->lastTime = time(NULL); } if (!interface->bufferList->firstNode) { DEBUG("interface %i: buffer empty\n", interface->num); freeList(interface->bufferList); interface->bufferList = NULL; } else if (ret < 0 && errno != EAGAIN && errno != EINTR) { /* cause interface to close */ DEBUG("interface %i: problems flushing buffer\n", interface->num); freeList(interface->bufferList); interface->bufferList = NULL; interface->expired = 1; } } int interfacePrintWithFD(int fd, char *buffer, int buflen) { static int i = 0; int copylen; Interface *interface; if (i >= interface_max_connections || !interfaces[i].open || interfaces[i].fd != fd) { for (i = 0; i < interface_max_connections; i++) { if (interfaces[i].open && interfaces[i].fd == fd) break; } if (i == interface_max_connections) return -1; } /* if fd isn't found or interfaces is going to be closed, do nothing */ if (interfaces[i].expired) return 0; interface = interfaces + i; while (buflen > 0 && !interface->expired) { copylen = buflen > interface->outBufSize - interface->outBuflen ? interface->outBufSize - interface->outBuflen : buflen; memcpy(interface->outBuffer + interface->outBuflen, buffer, copylen); buflen -= copylen; interface->outBuflen += copylen; buffer += copylen; if (interface->outBuflen >= interface->outBufSize) { printInterfaceOutBuffer(interface); } } return 0; } static void printInterfaceOutBuffer(Interface * interface) { char *buffer; int ret; if (!interface->open || interface->expired || !interface->outBuflen) { return; } if (interface->bufferList) { interface->outputBufferSize += sizeof(ListNode); interface->outputBufferSize += interface->outBuflen + 1; if (interface->outputBufferSize > interface_max_output_buffer_size) { ERROR("interface %i: output buffer size (%lli) is " "larger than the max (%lli)\n", interface->num, (long long)interface->outputBufferSize, (long long)interface_max_output_buffer_size); /* cause interface to close */ freeList(interface->bufferList); interface->bufferList = NULL; interface->expired = 1; } else { buffer = malloc(interface->outBuflen + 1); memcpy(buffer, interface->outBuffer, interface->outBuflen); buffer[interface->outBuflen] = '\0'; insertInListWithoutKey(interface->bufferList, (void *)buffer); flushInterfaceBuffer(interface); } } else { if ((ret = write(interface->fd, interface->outBuffer, interface->outBuflen)) < 0) { if (errno == EAGAIN || errno == EINTR) { buffer = malloc(interface->outBuflen + 1); memcpy(buffer, interface->outBuffer, interface->outBuflen); buffer[interface->outBuflen] = '\0'; interface->bufferList = makeList(free, 1); insertInListWithoutKey(interface->bufferList, (void *)buffer); } else { DEBUG("interface %i: problems writing\n", interface->num); interface->expired = 1; return; } } else if (ret < interface->outBuflen) { buffer = malloc(interface->outBuflen - ret + 1); memcpy(buffer, interface->outBuffer + ret, interface->outBuflen - ret); buffer[interface->outBuflen - ret] = '\0'; interface->bufferList = makeList(free, 1); insertInListWithoutKey(interface->bufferList, buffer); } /* if we needed to create buffer, initialize bufferSize info */ if (interface->bufferList) { DEBUG("interface %i: buffer created\n", interface->num); interface->outputBufferSize = sizeof(List); interface->outputBufferSize += sizeof(ListNode); interface->outputBufferSize += strlen((char *) interface-> bufferList-> firstNode->data) + 1; } } interface->outBuflen = 0; }