connection : IF FAILED host port type protocol urloption nettimeout retry rate1
THEN action1 recovery {
portset.timeout = $<number>8;
portset.retry = $<number>9;
/* This is a workaround to support content match without having to create
an URL object. 'urloption' creates the Request_T object we need minus the
URL object, but with enough information to perform content test.
TODO: Parser is in need of refactoring */
portset.url_request = urlrequest;
addeventaction(&(portset).action, $<number>12, $<number>13);
addport(&portset);
}
| IF FAILED URL URLOBJECT urloption nettimeout retry rate1
THEN action1 recovery {
prepare_urlrequest($<url>4);
portset.timeout = $<number>6;
portset.retry = $<number>7;
addeventaction(&(portset).action, $<number>10, $<number>11);
addport(&portset);
}
;
nettimeout : /* EMPTY */ {
$<number>$ = NET_TIMEOUT; // timeout is in milliseconds
}
| TIMEOUT NUMBER SECOND {
$<number>$ = $2 * 1000; // net timeout is in milliseconds internally
}
;
rate1 : /* EMPTY */
| NUMBER CYCLE {
rate1.count = $<number>1;
rate1.cycles = $<number>1;
if (rate1.cycles < 1 || rate1.cycles > BITMAP_MAX)
yyerror2("The number of cycles must be between 1 and %d", BITMAP_MAX);
}
| NUMBER NUMBER CYCLE {
rate1.count = $<number>1;
rate1.cycles = $<number>2;
if (rate1.cycles < 1 || rate1.cycles > BITMAP_MAX)
yyerror2("The number of cycles must be between 1 and %d", BITMAP_MAX);
if (rate1.count < 1 || rate1.count > rate1.cycles)
yyerror2("The number of events must be bigger then 0 and less than poll cycles");
}
;
/*
* Add the given portset to the current service's portlist
*/staticvoidaddport(Port_Tport){Port_Tp;ASSERT(port);NEW(p);p->port=port->port;p->type=port->type;p->socket=port->socket;p->family=port->family;p->action=port->action;p->timeout=port->timeout;p->retry=port->retry;p->request=port->request;p->generic=port->generic;p->protocol=port->protocol;p->pathname=port->pathname;p->hostname=port->hostname;p->url_request=port->url_request;p->request_checksum=port->request_checksum;p->request_hostheader=port->request_hostheader;p->http_headers=port->http_headers;p->version=port->version;p->operator=port->operator;p->status=port->status;memcpy(&p->ApacheStatus,&port->ApacheStatus,sizeof(structapache_status));if(p->request_checksum){cleanup_hash_string(p->request_checksum);if(strlen(p->request_checksum)==32)p->request_hashtype=HASH_MD5;elseif(strlen(p->request_checksum)==40)p->request_hashtype=HASH_SHA1;elseyyerror2("invalid checksum [%s]",p->request_checksum);}elsep->request_hashtype=0;if(port->SSL.use_ssl==TRUE){if(!have_ssl()){yyerror("ssl check cannot be activated. SSL is not supported");}else{if(port->SSL.certmd5!=NULL){p->SSL.certmd5=port->SSL.certmd5;cleanup_hash_string(p->SSL.certmd5);}p->SSL.use_ssl=TRUE;p->SSL.version=port->SSL.version;}}p->maxforward=port->maxforward;p->next=current->portlist;current->portlist=p;reset_portset();}
/** Defines a port object */typedefstructmyport{char*hostname;/**< Hostname to check */List_Thttp_headers;/**< Optional list of HTTP headers to send with request */char*request;/**< Specific protocol request */char*request_checksum;/**< The optional checksum for a req. document */char*request_hostheader;/**< The optional Host: header to use. Deprecated */char*pathname;/**< Pathname, in case of an UNIX socket */Generic_Tgeneric;/**< Generic test handle */volatileintsocket;/**< Socket used for connection */inttype;/**< Socket type used for connection (UDP/TCP) */intfamily;/**< Socket family used for connection (INET/UNIX) */intport;/**< Portnumber */intrequest_hashtype;/**< The optional type of hash for a req. document */intmaxforward;/**< Optional max forward for protocol checking */inttimeout;/**< The timeout in millseconds to wait for connect or read i/o */intretry;/**< Number of connection retry before reporting an error */intis_available;/**< TRUE if the server/port is available */intversion;/**< Protocol version */Operator_Typeoperator;/**< Comparison operator */intstatus;/**< Protocol status */doubleresponse;/**< Socket connection response time */EventAction_Taction;/**< Description of the action upon event occurence *//** Apache-status specific parameters */structapache_status{shortloglimit;/**< Max percentage of logging processes */shortloglimitOP;/**< loglimit operator */shortcloselimit;/**< Max percentage of closinging processes */shortcloselimitOP;/**< closelimit operator */shortdnslimit;/**< Max percentage of processes doing DNS lookup */shortdnslimitOP;/**< dnslimit operator */shortkeepalivelimit;/**< Max percentage of keepalive processes */shortkeepalivelimitOP;/**< keepalivelimit operator */shortreplylimit;/**< Max percentage of replying processes */shortreplylimitOP;/**< replylimit operator */shortrequestlimit;/**< Max percentage of processes reading requests */shortrequestlimitOP;/**< requestlimit operator */shortstartlimit;/**< Max percentage of processes starting up */shortstartlimitOP;/**< startlimit operator */shortwaitlimit;/**< Min percentage of processes waiting for connection */shortwaitlimitOP;/**< waitlimit operator */shortgracefullimit;/**< Max percentage of processes gracefully finishing */shortgracefullimitOP;/**< gracefullimit operator */shortcleanuplimit;/**< Max percentage of processes in idle cleanup */shortcleanuplimitOP;/**< cleanuplimit operator */}ApacheStatus;Ssl_TSSL;/**< SSL definition */Protocol_Tprotocol;/**< Protocol object for testing a port's service */Request_Turl_request;/**< Optional url client request object *//** For internal use */structmyport*next;/**< next port in chain */}*Port_T;
/**
* Validate a given process service s. Events are posted according to
* its configuration. In case of a fatal event FALSE is returned.
*/intcheck_process(Service_Ts){pid_tpid=-1;Port_Tpp=NULL;Resource_Tpr=NULL;ASSERT(s);/* Test for running process */if(!(pid=Util_isProcessRunning(s,FALSE))){Event_post(s,Event_Nonexist,STATE_FAILED,s->action_NONEXIST,"process is not running");returnFALSE;}else{Event_post(s,Event_Nonexist,STATE_SUCCEEDED,s->action_NONEXIST,"process is running with pid %d",(int)pid);}/* Reset the exec and timeout errors if active ... the process is running (most probably after manual intervention) */if(IS_EVENT_SET(s->error,Event_Exec))Event_post(s,Event_Exec,STATE_SUCCEEDED,s->action_EXEC,"process is running after previous exec error (slow starting or manually recovered?)");if(IS_EVENT_SET(s->error,Event_Timeout))for(ActionRate_Tar=s->actionratelist;ar;ar=ar->next)Event_post(s,Event_Timeout,STATE_SUCCEEDED,ar->action,"process is running after previous restart timeout (manually recovered?)");if(Run.doprocess){if(update_process_data(s,ptree,ptreesize,pid)){check_process_state(s);check_process_pid(s);check_process_ppid(s);if(s->uid)check_uid(s);if(s->euid)check_euid(s);if(s->gid)check_gid(s);if(s->uptimelist)check_uptime(s);for(pr=s->resourcelist;pr;pr=pr->next)check_process_resources(s,pr);}elseLogError("'%s' failed to get service data\n",s->name);}/* Test each host:port and protocol in the service's portlist */if(s->portlist)/* skip further tests during startup timeout */if(s->start)if(s->inf->priv.process.uptime<s->start->timeout)returnTRUE;for(pp=s->portlist;pp;pp=pp->next)check_connection(s,pp);returnTRUE;}
/**
* Test the connection and protocol
*/staticvoidcheck_connection(Service_Ts,Port_Tp){Socket_Tsocket;volatileintretry_count=p->retry;volatileintrv=TRUE;charbuf[STRLEN];charreport[STRLEN]={};structtimevalt1;structtimevalt2;ASSERT(s&&p);retry:/* Get time of connection attempt beginning */gettimeofday(&t1,NULL);/* Open a socket to the destination INET[hostname:port] or UNIX[pathname] */socket=socket_create(p);if(!socket){snprintf(report,STRLEN,"failed, cannot open a connection to %s",Util_portDescription(p,buf,sizeof(buf)));rv=FALSE;gotoerror;}else{DEBUG("'%s' succeeded connecting to %s\n",s->name,Util_portDescription(p,buf,sizeof(buf)));}if(p->protocol->check==check_default){if(socket_is_udp(socket)){// Only test "connected" UDP sockets without protocol, TCP connect is verified on create
if(!socket_is_ready(socket)){snprintf(report,STRLEN,"connection failed, %s is not ready for i|o -- %s",Util_portDescription(p,buf,sizeof(buf)),STRERROR);rv=FALSE;gotoerror;}}}/* Run the protocol verification routine through the socket */if(!p->protocol->check(socket)){snprintf(report,STRLEN,"failed protocol test [%s] at %s -- %s",p->protocol->name,Util_portDescription(p,buf,sizeof(buf)),socket_getError(socket));rv=FALSE;gotoerror;}else{DEBUG("'%s' succeeded testing protocol [%s] at %s\n",s->name,p->protocol->name,Util_portDescription(p,buf,sizeof(buf)));}/* Get time of connection attempt finish */gettimeofday(&t2,NULL);/* Get the response time */p->response=(double)(t2.tv_sec-t1.tv_sec)+(double)(t2.tv_usec-t1.tv_usec)/1000000;error:if(socket)socket_free(&socket);if(!rv){if(retry_count-->1){DEBUG("'%s' %s (attempt %d/%d)\n",s->name,report,p->retry-retry_count,p->retry);gotoretry;}p->response=-1;p->is_available=FALSE;Event_post(s,Event_Connection,STATE_FAILED,p->action,"%s",report);}else{p->is_available=TRUE;Event_post(s,Event_Connection,STATE_SUCCEEDED,p->action,"connection succeeded to %s",Util_portDescription(p,buf,sizeof(buf)));}}
/** Defines a protocol object with protocol functions */typedefstructProtocol_T{constchar*name;/**< Protocol name */int(*check)(Socket_T);/**< Protocol verification function */}*Protocol_T;
staticProtocol_Tprotocols[]={&(structProtocol_T){"DEFAULT",check_default},&(structProtocol_T){"HTTP",check_http},&(structProtocol_T){"FTP",check_ftp},&(structProtocol_T){"SMTP",check_smtp},&(structProtocol_T){"POP",check_pop},&(structProtocol_T){"IMAP",check_imap},&(structProtocol_T){"NNTP",check_nntp},&(structProtocol_T){"SSH",check_ssh},&(structProtocol_T){"DWP",check_dwp},&(structProtocol_T){"LDAP2",check_ldap2},&(structProtocol_T){"LDAP3",check_ldap3},&(structProtocol_T){"RDATE",check_rdate},&(structProtocol_T){"RSYNC",check_rsync},&(structProtocol_T){"generic",check_generic},&(structProtocol_T){"APACHESTATUS",check_apache_status},&(structProtocol_T){"NTP3",check_ntp3},&(structProtocol_T){"MYSQL",check_mysql},&(structProtocol_T){"DNS",check_dns},&(structProtocol_T){"POSTFIX-POLICY",check_postfix_policy},&(structProtocol_T){"TNS",check_tns},&(structProtocol_T){"PGSQL",check_pgsql},&(structProtocol_T){"CLAMAV",check_clamav},&(structProtocol_T){"SIP",check_sip},&(structProtocol_T){"LMTP",check_lmtp},&(structProtocol_T){"GPS",check_gps},&(structProtocol_T){"RADIUS",check_radius},&(structProtocol_T){"MEMCACHE",check_memcache},&(structProtocol_T){"WEBSOCKET",check_websocket},&(structProtocol_T){"REDIS",check_redis},&(structProtocol_T){"MONGODB",check_mongodb},&(structProtocol_T){"SIEVE",check_sieve}};/* ------------------------------------------------------------------ Public */Protocol_TProtocol_get(Protocol_Typetype){if(type>=sizeof(protocols)/sizeof(protocols[0]))returnprotocols[0];returnprotocols[type];}
intcheck_http(Socket_Tsocket){Port_TP;charhost[STRLEN];charauth[STRLEN]={};constchar*request=NULL;constchar*hostheader=NULL;ASSERT(socket);P=socket_get_Port(socket);ASSERT(P);request=P->request?P->request:"/";hostheader=_findHostHeaderIn(P->http_headers);hostheader=hostheader?hostheader:P->request_hostheader?P->request_hostheader:Util_getHTTPHostHeader(socket,host,STRLEN);// Otherwise use deprecated request_hostheader or default host
StringBuffer_Tsb=StringBuffer_create(168);StringBuffer_append(sb,"GET %s HTTP/1.1\r\n""Host: %s\r\n""Accept: */*\r\n""User-Agent: Monit/%s\r\n""%s",request,hostheader,VERSION,get_auth_header(P,auth,STRLEN));// Add headers if we have them
if(P->http_headers){for(list_tp=P->http_headers->head;p;p=p->next){char*header=p->e;if(Str_startsWith(header,"Host"))// Already set contrived above
continue;StringBuffer_append(sb,"%s\r\n",header);}}StringBuffer_append(sb,"\r\n");intsend_status=socket_write(socket,(void*)StringBuffer_toString(sb),StringBuffer_length(sb));StringBuffer_free(&sb);if(send_status<0){socket_setError(socket,"HTTP: error sending data -- %s",STRERROR);returnFALSE;}returncheck_request(socket,P);}
/**
* Check that the server returns a valid HTTP response as well as checksum
* or content regex if required
* @param s A socket
* @return TRUE if the response is valid otherwise FALSE
*/staticintcheck_request(Socket_Tsocket,Port_TP){intstatus,content_length=-1;charbuf[LINE_SIZE];if(!socket_readln(socket,buf,LINE_SIZE)){socket_setError(socket,"HTTP: Error receiving data -- %s",STRERROR);returnFALSE;}Str_chomp(buf);if(!sscanf(buf,"%*s %d",&status)){socket_setError(socket,"HTTP error: Cannot parse HTTP status in response: %s",buf);returnFALSE;}if(!Util_evalQExpression(P->operator,status,P->status)){socket_setError(socket,"HTTP error: Server returned status %d",status);returnFALSE;}/* Get Content-Length header value */while(socket_readln(socket,buf,LINE_SIZE)){if((buf[0]=='\r'&&buf[1]=='\n')||(buf[0]=='\n'))break;Str_chomp(buf);if(Str_startsWith(buf,"Content-Length")){if(!sscanf(buf,"%*s%*[: ]%d",&content_length)){socket_setError(socket,"HTTP error: Parsing Content-Length response header '%s'",buf);returnFALSE;}if(content_length<0){socket_setError(socket,"HTTP error: Illegal Content-Length response header '%s'",buf);returnFALSE;}}}if(P->url_request&&P->url_request->regex&&!do_regex(socket,content_length,P->url_request))returnFALSE;if(P->request_checksum)returncheck_request_checksum(socket,content_length,P->request_checksum,P->request_hashtype);returnTRUE;}