aboutsummaryrefslogblamecommitdiffstats
path: root/src/interface.c
blob: af669f89e2ae7d2c01e9d4bd428500a5c2713b0f (plain) (tree)
1
2
                                
                                                        
















                                                                            




                    

                       
                         
 


                   
                       
                     

                      
                      






                       
                                        
 






                                                                               
                                                                   
                                                                   
 
                                                                 
                                                                                       
                                                         

                                               
                                                
                                             

                           
                                                 
                         
                      


                                                     
                       
                        








                                                                                      



                       
                                    
 
                                                        
 
                                                           
 

                                                        
                  

                                     
 
                                    
                                 

                                                          



                                                                              













                                                                  
                                                      
 

                                                                    
                                                                            
                                          
                                                                            

                                                        


                                                             
 
                                                               


                                           



                                                 


                            
                                                         
 



                                                 


                                   
                                                         

 

                                                  

              
                                                                               
 
                                             
                                                    



                                                        
                             
                         




                                                                              


                                                                               



                              







                                                                               

                                                                               








                                                     
                                                    


         

                                                    
                    
                                                              
 


                                                                              
                 
         
 

                                                                 
                                                              





                                                                             
                                                              





                                                                         





                                                           




                                                                       
                                                              






                                                                       
                                                          
                                                           
                                
                                                                              
                                                                     
                         
                 

                                                                   

                                                                   
                                                     
                                
                                                                             

                                                                   
                                                     
                                
                        
                                                                       
                                                    
                                                           
                                                                             
                                                                    




                                                              



                                                           

         


                   

                                                                 
                    
 
                               

                                          

                                                                              
                                                            

                                                                       

                                                                             
                                                                        
                                                      
                                                          

                                         



                                                                         

                                                 

                                                                                

                 

         
                   

 

                                                    

                      


                                                                                
 


                                                                       
                                          

                         
 


                 


                                                                        


                     
                                            
 





                                                                



                 

                                                                        



                     





                                                                



                 

                                           






                          

                                                         
                                      

                                                                            






                                                               

                           




                          
                  



                       
                   
                          
 

                                                                              
 
                                                                    
 

                                                                  
 
                                 


                                                    


                                      




                                                                           



                                                                    

                                                                   



                                                                     
 

                               




                 

                         
              

                           
 
                                                  
 


                                                                    
                                                                            

                                                                      

                                           

         

                                              
                    
                                                                            
                                                                      
                                                                                
                                                                        

                                           

                                                                              
 

                                                           



                                                                            
                                                                               
                                                                               

                                           
                                                        

         

                                                            
                    
                                                                               

                                                                             
                                                                                
                                                                               

                                           
                                                         
         
 
                                                                           
 
                                                         




                                       

                                    



                     

                                                         




                                                         

                            


                             

                                      

 

                             

              



                                                                    
                                                                 


                                                                        

                                                                 



                 



                                                       

                    
                                                           
                                         



                                                                       


                                                



                                                                        



                                                 

                                                                      

                                                
                                                                  

                                                                 
                                      





                                                

                                                          
                         
                    
                             
 




                                                                         
                 

                                                   


                                                                               














                                                                           






                                                           


                                                          

                
                                                                              


                       




                                                                        
                                                                           



                                                                           



                                                        



                                                                  
                                                            

                                                                     

                                                        






                                                                          
                                                                    
                                                                          
                                                                             

                                                                       
                                                                         
                                                      


                                                       




                                                                        
                                                                  
                                                                              

                                                                               

                                                                                
                                                                   





                                                                                




                                 
/* 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 <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 */
	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;
}