/* the Music Player Daemon (MPD)
* (c)2003-2006 by Warren Dukes (shank@mercury.chem.pitt.edu)
* 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 <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#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 */
unsigned 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;
Interface * interfaces = NULL;
void flushInterfaceBuffer(Interface * interface);
void printInterfaceOutBuffer(Interface * interface);
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;
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);
}
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;
}
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;
}
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;
}
}
}
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;
}
}
}
void closeNextErroredInterface() {
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() {
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() {
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;
}
}
void closeAllInterfaces() {
int i;
fflush(NULL);
for(i=0;i<interface_max_connections;i++) {
if(interfaces[i].open) {
closeInterface(&(interfaces[i]));
}
}
}
void freeAllInterfaces() {
closeAllInterfaces();
free(interfaces);
interface_max_connections = 0;
}
void closeOldInterfaces() {
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]));
}
}
}
}
void closeInterfaceWithFD(int fd) {
int i;
for(i=0;i<interface_max_connections;i++) {
if(interfaces[i].fd==fd) {
closeInterface(&(interfaces[i]));
}
}
}
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;
}
}
void flushAllInterfaceBuffers() {
int i;
for(i=0;i<interface_max_connections;i++) {
if(interfaces[i].open && !interfaces[i].expired && interfaces[i].bufferList) {
flushInterfaceBuffer(&interfaces[i]);
}
}
}
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;
}
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;
}