blob: 57a2066c4d087c3c1edf9789b5634c3a4fe269a0 [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/************* ***************************
*
* WebIntrMain.cc - main loop for the Web Interface
*
*
*
****************************************************************************/
#include "libts.h"
#include "I_Layout.h"
#include "LocalManager.h"
#include "WebHttp.h"
#include "WebGlobals.h"
#include "MgmtUtils.h"
#include "WebMgmtUtils.h"
#include "WebIntrMain.h"
#include "Diags.h"
#include "MgmtSocket.h"
//INKqa09866
#include "TSControlMain.h"
#include "EventControlMain.h"
#if !defined(linux)
// Solaris header files forget this one
extern "C"
{
int usleep(unsigned int useconds);
}
#endif
/* Ugly hack - define HEADER_MD_5 to prevent the SSLeay md5.h
* header file from being included since it conflicts with the
* md5 implememntation from ink_code.h
*
* Additionally define HEAP_H and STACK_H to prevent stuff
* from the template library from being included which
* SUNPRO CC does not not like.
*/
// part of ugly hack described no longer needed
//#define HEADER_MD5_H
#define HEAP_H
#define STACK_H
typedef int fd;
#define SOCKET_TIMEOUT 10*60
WebInterFaceGlobals wGlobals;
// There are two web ports maintained
//
// One is for adminstration. This port serves
// all the configuration and monitoring info.
// Most sites will have some security features
// (authentication and SSL) active on this
// port since it system administrator access
// The other is for things that we want to serve
// insecurely. Client auto configuration falls
// in this catagory. The public key for the
// administration server is another example
//
WebContext autoconfContext;
// Used for storing argument values
int aconf_port_arg = -1;
// int checkWebContext(WebContext* wctx, char* desc)
//
// Checks out a WebContext to make sure that the
// directory exists and that the default file
// exists
//
// returns 0 if everthing is OK
// returns 1 if something is missing
//
int
checkWebContext(WebContext * wctx, const char *desc)
{
struct stat fInfo;
textBuffer defaultFile(256);
if (wctx->docRoot == NULL) {
mgmt_log(stderr, "[checkWebContext] No document root specified for %s\n", desc);
return 1;
}
if (stat(wctx->docRoot, &fInfo) < 0) {
mgmt_log(stderr, "[checkWebContext] Unable to access document root '%s' for %s : %s\n",
wctx->docRoot, desc, strerror(errno));
return 1;
}
if (!(fInfo.st_mode & S_IFDIR)) {
mgmt_log(stderr, "[checkWebContext] Document root '%s' for %s is not a directory\n", wctx->docRoot, desc);
return 1;
}
if (wctx->defaultFile == NULL) {
mgmt_log(stderr, "[checkWebContext] No default document specified for %s\n", desc);
return 1;
}
defaultFile.copyFrom(wctx->docRoot, strlen(wctx->docRoot));
defaultFile.copyFrom("/", 1);
defaultFile.copyFrom(wctx->defaultFile, strlen(wctx->defaultFile));
if (stat(defaultFile.bufPtr(), &fInfo) < 0) {
mgmt_log(stderr, "Unable to access default document, %s, for %s : %s\n", wctx->defaultFile, desc, strerror(errno));
return 1;
}
if (!(fInfo.st_mode & S_IFREG)) {
mgmt_log(stderr, "[checkWebContext] Default document for %s is not a file\n", desc);
return 1;
}
return 0;
}
// fd newUNIXsocket(char* fpath)
//
// returns a file descriptor associated with a new socket
// with the specified file path
//
// returns -1 if socket could not be created
//
// Thread Safe: NO! Call only from main Web interface thread
//
static fd
newUNIXsocket(char *fpath)
{
// coverity[var_decl]
struct sockaddr_un serv_addr;
int servlen;
fd socketFD;
int one = 1;
unlink(fpath);
socketFD = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketFD < 0) {
mgmt_log(stderr, "[newUNIXsocket] Unable to create socket: %s", strerror(errno));
return socketFD;
}
serv_addr.sun_family = AF_UNIX;
ink_strlcpy(serv_addr.sun_path, fpath, sizeof(serv_addr.sun_path));
#if defined(darwin) || defined(freebsd)
servlen = sizeof(struct sockaddr_un);
#else
servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
#endif
if (setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) {
mgmt_log(stderr, "[newUNIXsocket] Unable to set socket options: %s\n", strerror(errno));
}
if ((bind(socketFD, (struct sockaddr *) &serv_addr, servlen)) < 0) {
mgmt_log(stderr, "[newUNIXsocket] Unable to bind socket: %s\n", strerror(errno));
close_socket(socketFD);
return -1;
}
if (chmod(fpath, 00755) < 0) {
mgmt_log(stderr, "[newUNIXsocket] Unable to chmod unix-domain socket: %s\n", strerror(errno));
close_socket(socketFD);
return -1;
}
if ((listen(socketFD, 5)) < 0) {
mgmt_log(stderr, "[newUNIXsocket] Unable to listen on socket: %s", strerror(errno));
close_socket(socketFD);
return -1;
}
// Set the close on exec flag so our children do not
// have this socket open
if (fcntl(socketFD, F_SETFD, 1) < 0) {
mgmt_elog(stderr, errno, "[newUNIXSocket] Unable to set close on exec flag\n");
}
return socketFD;
}
// fd newTcpSocket(int port)
//
// returns a file descriptor associated with a new socket
// on the specified port
//
// If the socket could not be created, returns -1
//
// Thread Safe: NO! Call only from main Web interface thread
//
static fd
newTcpSocket(int port)
{
struct sockaddr_in socketInfo;
fd socketFD;
int one = 1;
memset(&socketInfo, 0, sizeof(sockaddr_in));
// Create the new TCP Socket
if ((socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
mgmt_fatal(stderr, errno, "[newTcpSocket]: %s", "Unable to Create Socket\n");
return -1;
}
// Specify our port number is network order
memset(&socketInfo, 0, sizeof(socketInfo));
socketInfo.sin_family = AF_INET;
socketInfo.sin_port = htons(port);
socketInfo.sin_addr.s_addr = htonl(INADDR_ANY);
// Allow for immediate re-binding to port
if (setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) {
mgmt_fatal(stderr, errno, "[newTcpSocket] Unable to set socket options.\n");
}
// Bind the port to the socket
if (bind(socketFD, (sockaddr *) & socketInfo, sizeof(socketInfo)) < 0) {
mgmt_elog(stderr, 0, "[newTcpSocket] Unable to bind port %d to socket: %s\n", port, strerror(errno));
close_socket(socketFD);
return -1;
}
// Listen on the new socket
if (listen(socketFD, 5) < 0) {
mgmt_elog(stderr, errno, "[newTcpSocket] %s\n", "Unable to listen on the socket");
close_socket(socketFD);
return -1;
}
// Set the close on exec flag so our children do not
// have this socket open
if (fcntl(socketFD, F_SETFD, 1) < 0) {
mgmt_elog(stderr, errno, "[newTcpSocket] Unable to set close on exec flag\n");
}
return socketFD;
}
// Keep track of the number of service threads for debugging
// purposes
static volatile int32_t numServiceThr = 0;
void *
serviceThrReaper(void * /* arg ATS_UNUSED */)
{
int numJoined;
lmgmt->syslogThrInit();
while (1) {
numJoined = 0;
// coverity[lock][lockagain]
ink_mutex_acquire(&wGlobals.serviceThrLock);
for (int i = 0; i < MAX_SERVICE_THREADS; i++) {
if (wGlobals.serviceThrArray[i].threadId != 0) {
if (wGlobals.serviceThrArray[i].waitingForJoin == true) {
//fprintf(stderr, "Joining on thread %d in slot %d\n",
// wGlobals.serviceThrArray[i].threadId, i);
// Join on threads that have exited so we recycle their thrIds and
// stack space
ink_assert(wGlobals.serviceThrArray[i].threadId > 0);
ink_thread_join(wGlobals.serviceThrArray[i].threadId);
wGlobals.serviceThrArray[i].fd = -1;
wGlobals.serviceThrArray[i].threadId = 0;
wGlobals.serviceThrArray[i].startTime = 0;
wGlobals.serviceThrArray[i].waitingForJoin = false;
wGlobals.serviceThrArray[i].alreadyShutdown = false;
numJoined++;
}
}
}
ink_mutex_release(&wGlobals.serviceThrLock);
for (int j = 0; j < numJoined; j++) {
#if defined(darwin)
ink_sem_post(wGlobals.serviceThrCount);
#else
ink_sem_post(&wGlobals.serviceThrCount);
#endif
ink_atomic_increment((int32_t *) & numServiceThr, -1);
}
usleep(300000);
}
return NULL;
} // END serviceThrReaper()
void *
webIntr_main(void *)
{
fd socketFD = -1; // FD for incoming HTTP connections
fd autoconfFD = -1; // FD for incoming autoconf connections
fd clientFD = -1; // FD for accepted connections
fd mgmtapiFD = -1; // FD for the api interface to issue commands
fd eventapiFD = -1; // FD for the api and clients to handle event callbacks
// dg: added init to get rid of compiler warnings
fd acceptFD = 0; // FD that is ready for accept
UIthr_t serviceThr = NO_THR; // type for new service thread
struct sockaddr_in *clientInfo; // Info about client connection
ink_thread thrId; // ID of service thread we just spawned
fd_set selectFDs; // FD set passed to select
int publicPort = -1; // Port for incoming autoconf connections
#if !defined(linux)
sigset_t allSigs; // Set of all signals
#endif
char pacFailMsg[] = "Auto-Configuration Service Failed to Initialize";
// char gphFailMsg[] = "Dynamic Graph Service Failed to Initialize";
char mgmtapiFailMsg[] = "Traffic server management API service Interface Failed to Initialize.";
RecInt tempInt;
bool found;
int autoconf_localhost_only = 0;
int addrLen;
int i;
#if !defined(linux)
// Start by blocking all signals
sigfillset(&allSigs);
ink_thread_sigsetmask(SIG_SETMASK, &allSigs, NULL);
#endif
lmgmt->syslogThrInit();
// Set up the threads management
#if defined(darwin)
static int qnum = 0;
char sname[NAME_MAX];
qnum++;
snprintf(sname,NAME_MAX,"%s%d","WebInterfaceMutex",qnum);
ink_sem_unlink(sname); // FIXME: remove, semaphore should be properly deleted after usage
wGlobals.serviceThrCount = ink_sem_open(sname, O_CREAT | O_EXCL, 0777, MAX_SERVICE_THREADS);
#else /* !darwin */
ink_sem_init(&wGlobals.serviceThrCount, MAX_SERVICE_THREADS);
#endif /* !darwin */
ink_mutex_init(&wGlobals.serviceThrLock, "Web Interface Mutex");
wGlobals.serviceThrArray = new serviceThr_t[MAX_SERVICE_THREADS];
for (i = 0; i < MAX_SERVICE_THREADS; i++) {
// coverity[missing_lock]
wGlobals.serviceThrArray[i].threadId = 0;
// coverity[missing_lock]
wGlobals.serviceThrArray[i].fd = -1;
// coverity[missing_lock]
wGlobals.serviceThrArray[i].startTime = 0;
// coverity[missing_lock]
wGlobals.serviceThrArray[i].waitingForJoin = false;
}
ink_thread_create(serviceThrReaper, NULL);
// Init mutex to only allow one submissions at a time
ink_mutex_init(&wGlobals.submitLock, "Submission Mutex");
// Fix for INKqa10514
found = (RecGetRecordInt("proxy.config.admin.autoconf.localhost_only", &tempInt) == REC_ERR_OKAY);
autoconf_localhost_only = (int) tempInt;
ink_assert(found);
// Set up the client autoconfiguration context
//
// Since autoconf is public access, turn security
// features off
if (aconf_port_arg > 0) {
publicPort = aconf_port_arg;
} else {
found = (RecGetRecordInt("proxy.config.admin.autoconf_port", &tempInt) == REC_ERR_OKAY);
publicPort = (int) tempInt;
ink_assert(found);
}
Debug("ui", "[WebIntrMain] Starting Client AutoConfig Server on Port %d\n", publicPort);
found = (RecGetRecordString_Xmalloc("proxy.config.admin.autoconf.doc_root", &(autoconfContext.docRoot)) == REC_ERR_OKAY);
ink_assert(found);
if (autoconfContext.docRoot == NULL) {
mgmt_fatal(stderr, 0, "[WebIntrMain] No Client AutoConf Root\n");
} else {
struct stat s;
int err;
if ((err = stat(autoconfContext.docRoot, &s)) < 0) {
ats_free(autoconfContext.docRoot);
autoconfContext.docRoot = ats_strdup(Layout::get()->sysconfdir);
if ((err = stat(autoconfContext.docRoot, &s)) < 0) {
mgmt_elog(0, "[WebIntrMain] unable to stat() directory '%s': %d %d, %s\n",
autoconfContext.docRoot, err, errno, strerror(errno));
mgmt_elog(0, "[WebIntrMain] please set the 'TS_ROOT' environment variable\n");
mgmt_fatal(stderr, 0, "[WebIntrMain] No Client AutoConf Root\n");
}
}
autoconfContext.docRootLen = strlen(autoconfContext.docRoot);
}
autoconfContext.defaultFile = "/proxy.pac";
// INKqa09866
// fire up interface for ts configuration through API; use absolute path from root to
// set up socket paths;
char api_sock_path[1024];
char event_sock_path[1024];
xptr<char> rundir(RecConfigReadRuntimeDir());
bzero(api_sock_path, 1024);
bzero(event_sock_path, 1024);
snprintf(api_sock_path, sizeof(api_sock_path), "%s/mgmtapisocket", (const char *)rundir);
snprintf(event_sock_path, sizeof(event_sock_path), "%s/eventapisocket", (const char *)rundir);
// INKqa12562: MgmtAPI sockets should be created with 775 permission
mode_t oldmask = umask(S_IWOTH);
if ((mgmtapiFD = newUNIXsocket(api_sock_path)) < 0) {
mgmt_log(stderr, "[WebIntrMain] Unable to set up socket for handling management API calls. API socket path = %s\n",
api_sock_path);
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_WEB_ERROR, mgmtapiFailMsg);
}
if ((eventapiFD = newUNIXsocket(event_sock_path)) < 0) {
mgmt_log(stderr,
"[WebIntrMain] Unable to set up so for handling management API event calls. Event Socket path: %s\n",
event_sock_path);
}
umask(oldmask);
// launch threads
// create thread for mgmtapi
ink_thread_create(ts_ctrl_main, &mgmtapiFD);
ink_thread_create(event_callback_main, &eventapiFD);
// initialize mgmt api plugins
// mgmt_plugin_init(config_path);
// Check our web contexts to make sure everything is
// OK. If it is, go ahead and fire up the interfaces
if (checkWebContext(&autoconfContext, "Browser Auto-Configuration") != 0) {
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_WEB_ERROR, pacFailMsg);
} else {
if ((autoconfFD = newTcpSocket(publicPort)) < 0) {
mgmt_elog(stderr, errno, "[WebIntrMain] Unable to start client autoconf server\n");
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_WEB_ERROR, pacFailMsg);
}
}
// Initialze WebHttp Module
WebHttpInit();
while (1) {
FD_ZERO(&selectFDs);
if (socketFD >= 0) {
FD_SET(socketFD, &selectFDs);
}
if (autoconfFD >= 0) {
FD_SET(autoconfFD, &selectFDs);
}
// TODO: Should we check return value?
mgmt_select(FD_SETSIZE, &selectFDs, (fd_set *) NULL, (fd_set *) NULL, NULL);
if (autoconfFD >= 0 && FD_ISSET(autoconfFD, &selectFDs)) {
acceptFD = autoconfFD;
serviceThr = AUTOCONF_THR;
} else {
ink_assert(!"[webIntrMain] Error on mgmt_select()\n");
}
#if defined(darwin)
ink_sem_wait(wGlobals.serviceThrCount);
#else
ink_sem_wait(&wGlobals.serviceThrCount);
#endif
ink_atomic_increment((int32_t *) & numServiceThr, 1);
// coverity[alloc_fn]
clientInfo = (struct sockaddr_in *)ats_malloc(sizeof(struct sockaddr_in));
addrLen = sizeof(struct sockaddr_in);
// coverity[noescape]
if ((clientFD = mgmt_accept(acceptFD, (sockaddr *) clientInfo, &addrLen)) < 0) {
mgmt_log(stderr, "[WebIntrMain]: %s%s\n", "Accept on incoming connection failed: ", strerror(errno));
#if defined(darwin)
ink_sem_post(wGlobals.serviceThrCount);
#else
ink_sem_post(&wGlobals.serviceThrCount);
#endif
ink_atomic_increment((int32_t *) & numServiceThr, -1);
} else { // Accept succeeded
if (safe_setsockopt(clientFD, IPPROTO_TCP, TCP_NODELAY, SOCKOPT_ON, sizeof(int)) < 0) {
mgmt_log(stderr, "[WebIntrMain]Failed to set sock options: %s\n", strerror(errno));
}
// Accept OK
ink_mutex_acquire(&wGlobals.serviceThrLock);
// If this a web manager, make sure that it is from an allowed ip addr
if (serviceThr == AUTOCONF_THR && autoconf_localhost_only != 0 &&
strcmp(inet_ntoa(clientInfo->sin_addr), "127.0.0.1") != 0) {
mgmt_log("WARNING: connect by disallowed client %s, closing\n", inet_ntoa(clientInfo->sin_addr));
#if defined(darwin)
ink_sem_post(wGlobals.serviceThrCount);
#else
ink_sem_post(&wGlobals.serviceThrCount);
#endif
ink_atomic_increment((int32_t *) & numServiceThr, -1);
ats_free(clientInfo);
close_socket(clientFD);
} else { // IP is allowed
for (i = 0; i < MAX_SERVICE_THREADS; i++) {
if (wGlobals.serviceThrArray[i].threadId == 0) {
//
wGlobals.serviceThrArray[i].fd = clientFD;
wGlobals.serviceThrArray[i].startTime = time(NULL);
wGlobals.serviceThrArray[i].waitingForJoin = false;
wGlobals.serviceThrArray[i].alreadyShutdown = false;
wGlobals.serviceThrArray[i].type = serviceThr;
wGlobals.serviceThrArray[i].clientInfo = clientInfo;
thrId = ink_thread_create(serviceThrMain, &wGlobals.serviceThrArray[i], 0);
// fprintf (stderr, "New Service Thread %d in slot %d\n", thrId, i);
if (thrId > 0) {
wGlobals.serviceThrArray[i].threadId = thrId;
} else {
// Failed to create thread
mgmt_elog(stderr, errno, "[WebIntrMain] Failed to create service thread\n");
wGlobals.serviceThrArray[i].threadId = 0;
wGlobals.serviceThrArray[i].fd = -1;
close_socket(clientFD);
#if defined(darwin)
ink_sem_post(wGlobals.serviceThrCount);
#else
ink_sem_post(&wGlobals.serviceThrCount);
#endif
ink_atomic_increment((int32_t *) & numServiceThr, -1);
}
break;
} else if (i == MAX_SERVICE_THREADS - 1) {
mgmt_fatal(stderr, 0, "[WebIntrMain] Syncronizaion Failure\n");
_exit(1);
}
}
}
ink_mutex_release(&wGlobals.serviceThrLock);
}
} // end while(1)
ink_release_assert(!"impossible"); // should never get here
return NULL;
}
// void* serviceThrMain(void* info)
//
// Thread main for any type of service thread
//
void *
serviceThrMain(void *info)
{
serviceThr_t *threadInfo = (serviceThr_t *) info;
WebHttpConInfo httpInfo;
lmgmt->syslogThrInit();
// Do our work
switch (threadInfo->type) {
case NO_THR: // dg: added to handle init value
ink_assert(false);
break;
case AUTOCONF_THR:
httpInfo.fd = threadInfo->fd;
httpInfo.context = &autoconfContext;
httpInfo.clientInfo = threadInfo->clientInfo;
WebHttpHandleConnection(&httpInfo);
break;
default:
// Handled here:
// GRAPH_THR
break;
}
ats_free(threadInfo->clientInfo);
// Mark ourselves ready to be reaped
ink_mutex_acquire(&wGlobals.serviceThrLock);
threadInfo->waitingForJoin = true;
threadInfo->fd = -1;
ink_mutex_release(&wGlobals.serviceThrLock);
// Call exit so that we properly release system resources
ink_thread_exit(NULL);
return NULL; // No Warning
}