2010-07-13 12:15:46 +00:00
|
|
|
/*
|
|
|
|
Serval Distributed Numbering Architecture (DNA)
|
|
|
|
Copyright (C) 2010 Paul Gardner-Stephen
|
|
|
|
|
|
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2012-02-05 05:45:19 +00:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
2012-02-23 02:15:42 +00:00
|
|
|
#include "serval.h"
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
unsigned char *hlr=NULL;
|
|
|
|
int hlr_size=0;
|
|
|
|
|
2011-05-05 09:10:38 +00:00
|
|
|
FILE *i_f=NULL;
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
struct in_addr client_addr;
|
|
|
|
int client_port;
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
int getBackingStore(char *s,int size);
|
|
|
|
int createServerSocket();
|
|
|
|
int simpleServerMode();
|
|
|
|
|
2012-02-05 05:45:19 +00:00
|
|
|
int recvwithttl(int sock,unsigned char *buffer,int bufferlen,int *ttl,
|
|
|
|
struct sockaddr *recvaddr,unsigned int *recvaddrlen)
|
|
|
|
{
|
|
|
|
struct msghdr msg;
|
|
|
|
struct iovec iov[1];
|
|
|
|
|
|
|
|
iov[0].iov_base=buffer;
|
|
|
|
iov[0].iov_len=bufferlen;
|
|
|
|
bzero(&msg,sizeof(msg));
|
|
|
|
msg.msg_name = recvaddr;
|
|
|
|
msg.msg_namelen = *recvaddrlen;
|
|
|
|
msg.msg_iov = &iov[0];
|
|
|
|
msg.msg_iovlen = 1;
|
|
|
|
// setting the following makes the data end up in the wrong place
|
|
|
|
// msg.msg_iov->iov_base=iov_buffer;
|
|
|
|
// msg.msg_iov->iov_len=sizeof(iov_buffer);
|
|
|
|
|
|
|
|
struct cmsghdr cmsgcmsg[16];
|
|
|
|
msg.msg_control = &cmsgcmsg[0];
|
|
|
|
msg.msg_controllen = sizeof(struct cmsghdr)*16;
|
|
|
|
msg.msg_flags = 0;
|
|
|
|
|
|
|
|
fcntl(sock,F_SETFL, O_NONBLOCK);
|
|
|
|
|
|
|
|
int len = recvmsg(sock,&msg,0);
|
2012-02-15 13:08:23 +00:00
|
|
|
|
|
|
|
if (debug&DEBUG_PACKETXFER)
|
|
|
|
fprintf(stderr,"recvmsg returned %d bytes (flags=%d,msg_controllen=%d)\n",
|
|
|
|
len,msg.msg_flags,msg.msg_controllen);
|
2012-02-05 05:45:19 +00:00
|
|
|
|
2012-02-15 13:08:23 +00:00
|
|
|
struct cmsghdr *cmsg;
|
|
|
|
if (len>0)
|
|
|
|
{
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg);
|
|
|
|
cmsg != NULL;
|
|
|
|
cmsg = CMSG_NXTHDR(&msg,cmsg)) {
|
|
|
|
|
|
|
|
if ((cmsg->cmsg_level == IPPROTO_IP) &&
|
|
|
|
((cmsg->cmsg_type == IP_RECVTTL) ||(cmsg->cmsg_type == IP_TTL))
|
|
|
|
&&(cmsg->cmsg_len) ){
|
|
|
|
if (debug&DEBUG_PACKETXFER)
|
|
|
|
fprintf(stderr," TTL (%p) data location resolves to %p\n",
|
|
|
|
ttl,CMSG_DATA(cmsg));
|
|
|
|
if (CMSG_DATA(cmsg)) {
|
|
|
|
*ttl = *(unsigned char *) CMSG_DATA(cmsg);
|
|
|
|
if (debug&DEBUG_PACKETXFER)
|
|
|
|
fprintf(stderr," TTL of packet is %d\n",*ttl);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (debug&DEBUG_PACKETXFER)
|
|
|
|
fprintf(stderr,"I didn't expect to see level=%02x, type=%02x\n",
|
|
|
|
cmsg->cmsg_level,cmsg->cmsg_type);
|
|
|
|
}
|
|
|
|
}
|
2012-02-05 05:45:19 +00:00
|
|
|
}
|
|
|
|
*recvaddrlen=msg.msg_namelen;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
int server(char *backing_file,int size,int foregroundMode)
|
|
|
|
{
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
/* Get backing store for HLR */
|
|
|
|
getBackingStore(backing_file,size);
|
|
|
|
|
|
|
|
if (overlayMode)
|
|
|
|
{
|
|
|
|
/* Now find and initialise all the suitable network interfaces, i.e.,
|
|
|
|
those running IPv4.
|
|
|
|
Packet radio dongles will get discovered later as the interfaces get probed.
|
|
|
|
|
|
|
|
This will setup the sockets for the server to communicate on each interface.
|
|
|
|
|
|
|
|
XXX - Problems may persist where the same address is used on multiple interfaces,
|
|
|
|
but otherwise hopefully it will allow us to bridge multiple networks.
|
|
|
|
*/
|
|
|
|
overlay_interface_discover();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Create a simple socket for listening on if we are not in overlay mesh mode. */
|
|
|
|
createServerSocket();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Detach from the console */
|
|
|
|
if (!foregroundMode) daemon(0,0);
|
|
|
|
|
2012-03-04 22:57:31 +00:00
|
|
|
/* Record PID */
|
|
|
|
char filename[1024];
|
|
|
|
char *instancepath=getenv("SERVALINSTANCE_PATH");
|
|
|
|
if (!instancepath) instancepath=DEFAULT_INSTANCE_PATH;
|
|
|
|
snprintf(filename,1023,"%s/serval.pid",instancepath); filename[1023]=0;
|
|
|
|
FILE *f=fopen(filename,"w");
|
|
|
|
if (!f) {
|
|
|
|
WHY("Could not write to PID file");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
fprintf(f,"%d\n",getpid());
|
|
|
|
fclose(f);
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
if (!overlayMode) simpleServerMode();
|
2011-08-14 08:36:39 +00:00
|
|
|
else overlayServerMode();
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getBackingStore(char *backing_file,int size)
|
|
|
|
{
|
|
|
|
if (!backing_file)
|
2010-07-13 12:15:46 +00:00
|
|
|
{
|
|
|
|
/* transitory storage of HLR data, so just malloc() the memory */
|
|
|
|
hlr=calloc(size,1);
|
|
|
|
if (!hlr) exit(setReason("Failed to calloc() HLR database."));
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Allocated %d byte temporary HLR store\n",size);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned char zero[8192];
|
|
|
|
FILE *f=fopen(backing_file,"r+");
|
|
|
|
if (!f) f=fopen(backing_file,"w+");
|
|
|
|
if (!f) exit(setReason("Could not open backing file."));
|
|
|
|
bzero(&zero[0],8192);
|
|
|
|
fseek(f,0,SEEK_END);
|
|
|
|
errno=0;
|
|
|
|
while(ftell(f)<size)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
fseek(f,0,SEEK_END);
|
|
|
|
if ((r=fwrite(zero,8192,1,f))!=1)
|
|
|
|
{
|
|
|
|
perror("fwrite");
|
|
|
|
exit(setReason("Could not enlarge backing file to requested size (short write)"));
|
|
|
|
}
|
|
|
|
fseek(f,0,SEEK_END);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (errno) perror("fseek");
|
|
|
|
if (fwrite("",1,1,f)!=1)
|
|
|
|
{
|
|
|
|
fprintf(stderr,"Failed to set backing file size.\n");
|
|
|
|
perror("fwrite");
|
|
|
|
}
|
|
|
|
hlr=(unsigned char *)mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_NORESERVE,fileno(f),0);
|
|
|
|
if (hlr==MAP_FAILED) {
|
|
|
|
perror("mmap");
|
|
|
|
exit(setReason("Memory mapping of HLR backing file failed."));
|
|
|
|
}
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Allocated %d byte HLR store backed by file `%s'\n",
|
2010-07-13 12:15:46 +00:00
|
|
|
size,backing_file);
|
|
|
|
}
|
|
|
|
hlr_size=size;
|
2011-08-09 06:10:27 +00:00
|
|
|
|
|
|
|
seedHlr();
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
return 0;
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int processRequest(unsigned char *packet,int len,
|
|
|
|
struct sockaddr *sender,int sender_len,
|
2012-02-05 05:45:19 +00:00
|
|
|
unsigned char *transaction_id,int recvttl, char *did,char *sid)
|
2010-07-13 12:15:46 +00:00
|
|
|
{
|
|
|
|
/* Find HLR entry by DID or SID, unless creating */
|
2011-03-22 06:33:14 +00:00
|
|
|
int ofs,rofs=0;
|
2010-07-13 12:15:46 +00:00
|
|
|
int records_searched=0;
|
|
|
|
|
|
|
|
int prev_pofs=0;
|
|
|
|
int pofs=OFS_PAYLOAD;
|
|
|
|
|
|
|
|
while(pofs<len)
|
|
|
|
{
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr," processRequest: len=%d, pofs=%d, pofs_prev=%d\n",len,pofs,prev_pofs);
|
2010-07-13 12:15:46 +00:00
|
|
|
/* Avoid infinite loops */
|
|
|
|
if (pofs<=prev_pofs) break;
|
|
|
|
prev_pofs=pofs;
|
|
|
|
|
|
|
|
if (packet[pofs]==ACTION_CREATEHLR)
|
|
|
|
{
|
2012-03-06 08:36:07 +00:00
|
|
|
/* Creating an HLR requires an initial DID number and definitely no SID -
|
2010-07-13 12:15:46 +00:00
|
|
|
you can't choose a SID. */
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Creating a new HLR record. did='%s', sid='%s'\n",did,sid);
|
2012-02-05 05:45:19 +00:00
|
|
|
if (!did[0]) return respondSimple(NULL,ACTION_DECLINED,NULL,0,transaction_id,recvttl,sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
|
|
|
if (sid[0]) return respondSimple(sid,ACTION_DECLINED,NULL,0,transaction_id,recvttl,sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Verified that create request supplies DID but not SID\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
{
|
2012-03-06 08:36:07 +00:00
|
|
|
/* Look for an existing HLR with the requested DID. If there is one, respond with its
|
|
|
|
SID. This handles duplicates of the same message. If there is none, then make a new
|
|
|
|
HLR with random SID and initial DID. */
|
|
|
|
int ofs = 0;
|
2012-03-13 08:01:29 +00:00
|
|
|
int response = ACTION_DECLINED;
|
|
|
|
if (findHlr(hlr, &ofs, sid, did)) {
|
|
|
|
hlrSid(hlr, ofs, sid);
|
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"HLR found with did='%s' at ofs=%x: sid='%s'\n", did, ofs, sid);
|
|
|
|
response = ACTION_OKAY;
|
2012-03-06 08:36:07 +00:00
|
|
|
}
|
2012-03-13 08:01:29 +00:00
|
|
|
else if (createHlr(did, sid) == 0) {
|
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"HLR created with did='%s': sid='%s'\n", did, sid);
|
|
|
|
response = ACTION_OKAY;
|
|
|
|
}
|
|
|
|
return respondSimple(sid, response, NULL, 0, transaction_id, recvttl, sender, CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
pofs+=1;
|
|
|
|
pofs+=1+SID_SIZE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr,"Looking at action code 0x%02x @ packet offset 0x%x\n",
|
2011-10-05 22:46:47 +00:00
|
|
|
packet[pofs],pofs);
|
2010-07-13 12:15:46 +00:00
|
|
|
switch(packet[pofs])
|
|
|
|
{
|
|
|
|
case ACTION_PAD: /* Skip padding */
|
|
|
|
pofs++;
|
|
|
|
pofs+=1+packet[pofs];
|
|
|
|
break;
|
|
|
|
case ACTION_EOT: /* EOT */
|
|
|
|
pofs=len;
|
|
|
|
break;
|
2011-05-05 09:10:38 +00:00
|
|
|
case ACTION_STATS:
|
|
|
|
/* short16 variable id,
|
|
|
|
int32 value */
|
|
|
|
{
|
|
|
|
pofs++;
|
|
|
|
short field=packet[pofs+1]+(packet[pofs]<<8);
|
|
|
|
int value=packet[pofs+5]+(packet[pofs+4]<<8)+(packet[pofs+3]<<16)+(packet[pofs+2]<<24);
|
|
|
|
pofs+=6;
|
|
|
|
if (instrumentation_file)
|
|
|
|
{
|
|
|
|
if (!i_f) { if (strcmp(instrumentation_file,"-")) i_f=fopen(instrumentation_file,"a"); else i_f=stdout; }
|
|
|
|
if (i_f) fprintf(i_f,"%ld:%08x:%d:%d\n",time(0),*(unsigned int *)&sender->sa_data[0],field,value);
|
|
|
|
if (i_f) fflush(i_f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2011-05-23 11:35:15 +00:00
|
|
|
case ACTION_DIGITALTELEGRAM:
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr,"In ACTION_DIGITALTELEGRAM\n");
|
2011-05-23 11:35:15 +00:00
|
|
|
{
|
2012-03-06 03:59:47 +00:00
|
|
|
// Unpack SMS message.
|
|
|
|
char emitterPhoneNumber[256];
|
|
|
|
char message[256];
|
|
|
|
pofs++;
|
|
|
|
/* char messageType = packet[pofs]; */
|
|
|
|
pofs++;
|
|
|
|
char emitterPhoneNumberLen = packet[pofs];
|
|
|
|
pofs++;
|
|
|
|
char messageLen = packet[pofs];
|
|
|
|
pofs++;
|
|
|
|
strncpy(emitterPhoneNumber, (const char*)packet+pofs, emitterPhoneNumberLen);
|
|
|
|
emitterPhoneNumber[(unsigned int)emitterPhoneNumberLen]=0;
|
2011-05-26 04:55:59 +00:00
|
|
|
|
2012-03-06 03:59:47 +00:00
|
|
|
pofs+=emitterPhoneNumberLen;
|
|
|
|
strncpy(message, (const char*)packet+pofs, messageLen);
|
|
|
|
message[(unsigned int)messageLen]=0;
|
|
|
|
|
|
|
|
pofs+=messageLen;
|
|
|
|
|
|
|
|
// Check if I'm the recipient
|
|
|
|
ofs=0;
|
|
|
|
if (findHlr(hlr, &ofs, sid, did)) {
|
|
|
|
// Check transaction cache to see if message has already been delivered. If not,
|
|
|
|
// then deliver it now.
|
|
|
|
if (!isTransactionInCache(transaction_id)) {
|
2012-03-01 03:06:36 +00:00
|
|
|
// Deliver SMS to android.
|
|
|
|
char amCommand[576]; // 64 char + 2*256(max) char = 576
|
|
|
|
sprintf(amCommand, "am broadcast -a org.servalproject.DT -e number \"%s\" -e content \"%s\"", emitterPhoneNumber, message);
|
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr,"Delivering DT message via intent: %s\n",amCommand);
|
|
|
|
runCommand(amCommand);
|
|
|
|
// Record in cache to prevent re-delivering the same message if a duplicate is received.
|
|
|
|
insertTransactionInCache(transaction_id);
|
|
|
|
}
|
2012-03-13 08:01:29 +00:00
|
|
|
char sid[SID_STRLEN+1];
|
|
|
|
respondSimple(hlrSid(hlr, ofs, sid), ACTION_OKAY, NULL, 0, transaction_id, recvttl, sender, CRYPT_CIPHERED|CRYPT_SIGNED);
|
2011-05-23 21:52:43 +00:00
|
|
|
}
|
2011-03-22 06:33:14 +00:00
|
|
|
}
|
|
|
|
break;
|
2010-07-13 12:15:46 +00:00
|
|
|
case ACTION_SET:
|
|
|
|
ofs=0;
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr,"Looking for hlr entries with sid='%s' / did='%s'\n",sid,did);
|
2011-04-27 02:47:26 +00:00
|
|
|
|
|
|
|
if ((!sid)||(!sid[0])) {
|
|
|
|
setReason("You can only set variables by SID");
|
2012-02-05 05:45:19 +00:00
|
|
|
return respondSimple(NULL,ACTION_ERROR,
|
|
|
|
(unsigned char *)"SET requires authentication by SID",
|
|
|
|
0,transaction_id,recvttl,
|
2011-08-12 19:34:38 +00:00
|
|
|
sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2011-04-27 02:47:26 +00:00
|
|
|
}
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
while(findHlr(hlr,&ofs,sid,did))
|
|
|
|
{
|
2011-03-30 05:04:23 +00:00
|
|
|
int itemId,instance,start_offset,bytes,flags;
|
|
|
|
unsigned char value[9000],oldvalue[65536];
|
|
|
|
int oldr,oldl;
|
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"findHlr found a match for writing at 0x%x\n",ofs);
|
|
|
|
if (debug&DEBUG_HLR) hlrDump(hlr,ofs);
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
/* XXX consider taking action on this HLR
|
|
|
|
(check PIN first depending on the action requested) */
|
|
|
|
|
|
|
|
/* XXX Doesn't verify PIN authentication */
|
|
|
|
|
|
|
|
/* Get write request */
|
|
|
|
|
|
|
|
pofs++; rofs=pofs;
|
|
|
|
if (extractRequest(packet,&pofs,len,
|
|
|
|
&itemId,&instance,value,
|
|
|
|
&start_offset,&bytes,&flags))
|
|
|
|
{
|
|
|
|
setReason("Could not extract ACTION_SET request");
|
|
|
|
return
|
2012-02-05 05:45:19 +00:00
|
|
|
respondSimple(NULL,ACTION_ERROR,
|
|
|
|
(unsigned char *)"Mal-formed SET request",
|
|
|
|
0,transaction_id,recvttl,
|
2011-08-12 19:34:38 +00:00
|
|
|
sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the stored value */
|
|
|
|
oldl=65536;
|
|
|
|
oldr=hlrGetVariable(hlr,ofs,itemId,instance,oldvalue,&oldl);
|
|
|
|
if (oldr) {
|
|
|
|
if (flags==SET_NOREPLACE) {
|
|
|
|
oldl=0;
|
|
|
|
} else {
|
|
|
|
setReason("Tried to SET_NOCREATE/SET_REPLACE a non-existing value");
|
|
|
|
return
|
|
|
|
respondSimple(NULL,ACTION_ERROR,
|
|
|
|
(unsigned char *)"Cannot SET NOCREATE/REPLACE a value that does not exist",
|
2012-02-05 05:45:19 +00:00
|
|
|
0,transaction_id,recvttl,sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (flags==SET_NOREPLACE) {
|
|
|
|
setReason("Tried to SET_NOREPLACE an existing value");
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) dump("Existing value (in SET_NOREPLACE flagged request)",oldvalue,oldl);
|
2010-07-13 12:15:46 +00:00
|
|
|
return
|
|
|
|
respondSimple(NULL,ACTION_ERROR,
|
|
|
|
(unsigned char *)"Cannot SET NOREPLACE; a value exists",
|
2012-02-05 05:45:19 +00:00
|
|
|
0,transaction_id,recvttl,sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Replace the changed portion of the stored value */
|
|
|
|
if ((start_offset+bytes)>oldl) {
|
|
|
|
bzero(&oldvalue[oldl],start_offset+bytes-oldl);
|
|
|
|
oldl=start_offset+bytes;
|
|
|
|
}
|
|
|
|
bcopy(&value[0],&oldvalue[start_offset],bytes);
|
|
|
|
|
|
|
|
/* Write new value back */
|
|
|
|
if (hlrSetVariable(hlr,ofs,itemId,instance,oldvalue,oldl))
|
|
|
|
{
|
|
|
|
setReason("Failed to write variable");
|
|
|
|
return
|
2012-02-05 05:45:19 +00:00
|
|
|
respondSimple(NULL,ACTION_ERROR,
|
|
|
|
(unsigned char *)"Failed to SET variable",
|
|
|
|
0,transaction_id,recvttl,
|
2011-08-12 19:34:38 +00:00
|
|
|
sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) { fprintf(stderr,"HLR after writing:\n"); hlrDump(hlr,ofs); }
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
/* Reply that we wrote the fragment */
|
|
|
|
respondSimple(sid,ACTION_WROTE,&packet[rofs],6,
|
2012-02-05 05:45:19 +00:00
|
|
|
transaction_id,recvttl,
|
|
|
|
sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
/* Advance to next record and keep searching */
|
|
|
|
if (nextHlr(hlr,&ofs)) break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ACTION_GET:
|
2011-04-27 02:47:26 +00:00
|
|
|
{
|
|
|
|
/* Limit transfer size to MAX_DATA_BYTES, plus an allowance for variable packing. */
|
|
|
|
unsigned char data[MAX_DATA_BYTES+16];
|
|
|
|
int dlen=0;
|
|
|
|
int sendDone=0;
|
2011-03-30 05:04:23 +00:00
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) dump("Request bytes",&packet[pofs],8);
|
2011-08-29 06:50:27 +00:00
|
|
|
|
|
|
|
pofs++;
|
|
|
|
int var_id=packet[pofs];
|
|
|
|
int instance=-1;
|
|
|
|
if (var_id&0x80) instance=packet[++pofs];
|
|
|
|
pofs++;
|
|
|
|
int offset=(packet[pofs]<<8)+packet[pofs+1]; pofs+=2;
|
2012-03-13 08:01:29 +00:00
|
|
|
char sid[SID_STRLEN+1];
|
2011-08-29 06:50:27 +00:00
|
|
|
char *hlr_sid=NULL;
|
|
|
|
|
2011-10-05 22:57:04 +00:00
|
|
|
pofs+=2;
|
2011-08-29 06:50:27 +00:00
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr,"Processing ACTION_GET (var_id=%02x, instance=%02x, pofs=0x%x, len=%d)\n",var_id,instance,pofs,len);
|
2010-07-13 12:15:46 +00:00
|
|
|
|
2011-04-27 02:47:26 +00:00
|
|
|
ofs=0;
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Looking for hlr entries with sid='%s' / did='%s'\n",sid?sid:"null",did?did:"null");
|
2011-04-27 02:47:26 +00:00
|
|
|
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
struct hlrentry_handle *h;
|
|
|
|
|
|
|
|
// if an empty did was passed in, get results from all hlr records
|
|
|
|
if (*sid || *did){
|
|
|
|
if (!findHlr(hlr,&ofs,sid,did)) break;
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"findHlr found a match @ 0x%x\n",ofs);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) hlrDump(hlr,ofs);
|
2011-04-27 02:47:26 +00:00
|
|
|
|
|
|
|
/* XXX consider taking action on this HLR
|
|
|
|
(check PIN first depending on the action requested) */
|
|
|
|
|
|
|
|
/* Form a reply packet containing the requested data */
|
|
|
|
|
|
|
|
if (instance==0xff) instance=-1;
|
|
|
|
|
|
|
|
/* Step through HLR to find any matching instances of the requested variable */
|
|
|
|
h=openhlrentry(hlr,ofs);
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"openhlrentry(hlr,%d) returned %p\n",ofs,h);
|
2011-04-27 02:47:26 +00:00
|
|
|
while(h)
|
|
|
|
{
|
|
|
|
/* Is this the variable? */
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr," considering var_id=%02x, instance=%02x\n",
|
2011-04-27 02:47:26 +00:00
|
|
|
h->var_id,h->var_instance);
|
|
|
|
if (h->var_id==var_id)
|
|
|
|
{
|
|
|
|
if (h->var_instance==instance||instance==-1)
|
|
|
|
{
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Sending matching variable value instance (instance #%d), value offset %d.\n",
|
2011-04-27 02:47:26 +00:00
|
|
|
h->var_instance,offset);
|
|
|
|
|
|
|
|
// only send each value when the *next* record is found, that way we can easily stamp the last response with DONE
|
|
|
|
if (sendDone>0)
|
2012-02-05 05:45:19 +00:00
|
|
|
respondSimple(hlr_sid,ACTION_DATA,data,dlen,
|
|
|
|
transaction_id,recvttl,sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2011-04-27 02:47:26 +00:00
|
|
|
|
|
|
|
dlen=0;
|
|
|
|
|
|
|
|
if (packageVariableSegment(data,&dlen,h,offset,MAX_DATA_BYTES+16))
|
|
|
|
return setReason("packageVariableSegment() failed.");
|
2012-03-13 08:01:29 +00:00
|
|
|
hlr_sid = hlrSid(hlr, ofs, sid);
|
2011-04-27 02:47:26 +00:00
|
|
|
|
|
|
|
sendDone++;
|
|
|
|
}
|
|
|
|
else
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Ignoring variable instance %d (not %d)\n",
|
2011-04-27 02:47:26 +00:00
|
|
|
h->var_instance,instance);
|
|
|
|
}
|
|
|
|
else
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Ignoring variable ID %d (not %d)\n",
|
2011-04-27 02:47:26 +00:00
|
|
|
h->var_id,var_id);
|
|
|
|
h=hlrentrygetent(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Advance to next record and keep searching */
|
|
|
|
if (nextHlr(hlr,&ofs)) break;
|
|
|
|
}
|
2010-07-13 12:15:46 +00:00
|
|
|
if (sendDone)
|
|
|
|
{
|
2011-04-27 02:47:26 +00:00
|
|
|
data[dlen++]=ACTION_DONE;
|
|
|
|
data[dlen++]=sendDone&0xff;
|
2012-02-05 05:45:19 +00:00
|
|
|
respondSimple(hlr_sid,ACTION_DATA,data,dlen,transaction_id,
|
|
|
|
recvttl,sender,CRYPT_CIPHERED|CRYPT_SIGNED);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
2011-08-10 13:38:59 +00:00
|
|
|
if (gatewayspec&&(var_id==VAR_LOCATIONS)&&did&&strlen(did))
|
2011-05-06 03:16:04 +00:00
|
|
|
{
|
|
|
|
/* We are a gateway, so offer connection via the gateway as well */
|
|
|
|
unsigned char data[MAX_DATA_BYTES+16];
|
|
|
|
int dlen=0;
|
|
|
|
struct hlrentry_handle fake;
|
|
|
|
unsigned char uri[1024];
|
2011-05-16 07:28:25 +00:00
|
|
|
|
|
|
|
/* We use asterisk to provide the gateway service,
|
|
|
|
so we need to create a temporary extension in extensions.conf,
|
|
|
|
ask asterisk to re-read extensions.conf, and then make sure it has
|
|
|
|
a functional SIP gateway.
|
|
|
|
*/
|
2011-05-23 21:52:43 +00:00
|
|
|
if (!asteriskObtainGateway(sid,did,(char *)uri))
|
2011-05-16 07:28:25 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
fake.value_len=strlen((char *)uri);
|
|
|
|
fake.var_id=var_id;
|
|
|
|
fake.value=uri;
|
|
|
|
|
|
|
|
if (packageVariableSegment(data,&dlen,&fake,offset,MAX_DATA_BYTES+16))
|
|
|
|
return setReason("packageVariableSegment() of gateway URI failed.");
|
|
|
|
|
2012-03-13 08:01:29 +00:00
|
|
|
char sid[SID_STRLEN+1];
|
|
|
|
respondSimple(hlrSid(hlr, 0, sid),ACTION_DATA,data,dlen,
|
2012-02-05 05:45:19 +00:00
|
|
|
transaction_id,recvttl,sender,
|
|
|
|
CRYPT_CIPHERED|CRYPT_SIGNED);
|
2011-05-16 07:28:25 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Should we indicate the gateway is not available? */
|
|
|
|
}
|
2011-05-06 03:16:04 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 02:47:26 +00:00
|
|
|
}
|
2010-07-13 12:15:46 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setReason("Asked to perform unsupported action");
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_PACKETFORMATS) fprintf(stderr,"Asked to perform unsipported action at Packet offset = 0x%x\n",pofs);
|
|
|
|
if (debug&DEBUG_PACKETFORMATS) dump("Packet",packet,len);
|
2012-01-08 18:00:23 +00:00
|
|
|
return WHY("Asked to perform unsupported action.");
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_HLR) fprintf(stderr,"Searched %d HLR entries.\n",records_searched);
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int respondSimple(char *sid,int action,unsigned char *action_text,int action_len,
|
2012-02-05 05:45:19 +00:00
|
|
|
unsigned char *transaction_id,int recvttl,
|
|
|
|
struct sockaddr *recvaddr,int cryptoFlags)
|
2010-07-13 12:15:46 +00:00
|
|
|
{
|
|
|
|
unsigned char packet[8000];
|
|
|
|
int pl=0;
|
|
|
|
int *packet_len=&pl;
|
|
|
|
int packet_maxlen=8000;
|
|
|
|
int i;
|
|
|
|
|
2011-05-16 07:28:25 +00:00
|
|
|
/* XXX Complain about invalid crypto flags.
|
|
|
|
XXX We don't do anything with the crypto flags right now
|
|
|
|
XXX Other packet sending routines need this as well. */
|
2012-01-08 18:00:23 +00:00
|
|
|
if (!cryptoFlags) return WHY("Crypto-flags not set.");
|
2011-05-16 07:28:25 +00:00
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
/* ACTION_ERROR is associated with an error message.
|
|
|
|
For syntactic simplicity, we do not require the respondSimple() call to provide
|
|
|
|
the length of the error message. */
|
|
|
|
if (action==ACTION_ERROR) {
|
|
|
|
action_len=strlen((char *)action_text);
|
|
|
|
/* Make sure the error text isn't too long.
|
|
|
|
IF it is, trim it, as we still need to communicate the error */
|
|
|
|
if (action_len>255) action_len=255;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prepare the request packet */
|
2012-01-08 18:00:23 +00:00
|
|
|
if (packetMakeHeader(packet,8000,packet_len,transaction_id,cryptoFlags))
|
|
|
|
return WHY("packetMakeHeader() failed.");
|
2010-07-13 12:15:46 +00:00
|
|
|
if (sid&&sid[0])
|
|
|
|
{ if (packetSetSid(packet,8000,packet_len,sid))
|
|
|
|
return setReason("invalid SID in reply"); }
|
|
|
|
else
|
|
|
|
{ if (packetSetDid(packet,8000,packet_len,""))
|
|
|
|
return setReason("Could not set empty DID in reply"); }
|
|
|
|
|
|
|
|
CHECK_PACKET_LEN(1+1+action_len);
|
|
|
|
packet[(*packet_len)++]=action;
|
|
|
|
if (action==ACTION_ERROR) packet[(*packet_len)++]=action_len;
|
|
|
|
for(i=0;i<action_len;i++) packet[(*packet_len)++]=action_text[i];
|
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNARESPONSES) dump("Simple response octets",action_text,action_len);
|
2010-07-13 12:15:46 +00:00
|
|
|
|
2012-02-05 05:45:19 +00:00
|
|
|
if (packetFinalise(packet,8000,recvttl,packet_len,cryptoFlags))
|
2012-01-08 18:00:23 +00:00
|
|
|
return WHY("packetFinalise() failed.");
|
2010-07-13 12:15:46 +00:00
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNARESPONSES) fprintf(stderr,"Sending response of %d bytes.\n",*packet_len);
|
2010-07-13 12:15:46 +00:00
|
|
|
|
2012-01-08 18:00:23 +00:00
|
|
|
if (packetSendRequest(REQ_REPLY,packet,*packet_len,NONBATCH,transaction_id,recvaddr,NULL))
|
|
|
|
return WHY("packetSendRequest() failed.");
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2011-08-08 14:41:46 +00:00
|
|
|
|
|
|
|
int createServerSocket()
|
|
|
|
{
|
|
|
|
struct sockaddr_in bind_addr;
|
|
|
|
|
|
|
|
sock=socket(PF_INET,SOCK_DGRAM,0);
|
|
|
|
if (sock<0) {
|
|
|
|
fprintf(stderr,"Could not create UDP socket.\n");
|
|
|
|
perror("socket");
|
|
|
|
exit(-3);
|
|
|
|
}
|
|
|
|
|
2012-02-23 01:29:06 +00:00
|
|
|
/* Automatically close socket on calls to exec().
|
|
|
|
This makes life easier when we restart with an exec after receiving
|
|
|
|
a bad signal. */
|
|
|
|
fcntl(sock, F_SETFL,
|
|
|
|
fcntl(sock, F_GETFL, NULL)|O_CLOEXEC);
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
int TRUE=1;
|
|
|
|
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &TRUE, sizeof(TRUE));
|
2012-02-05 05:45:19 +00:00
|
|
|
|
|
|
|
errno=0;
|
|
|
|
if(setsockopt(sock, IPPROTO_IP, IP_RECVTTL, &TRUE,sizeof(TRUE))<0)
|
|
|
|
perror("setsockopt(IP_RECVTTL)");
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
bind_addr.sin_family = AF_INET;
|
|
|
|
bind_addr.sin_port = htons( PORT_DNA );
|
|
|
|
bind_addr.sin_addr.s_addr = htonl( INADDR_ANY );
|
|
|
|
if(bind(sock,(struct sockaddr *)&bind_addr,sizeof(bind_addr))) {
|
|
|
|
fprintf(stderr,"MP HLR server could not bind to UDP port %d\n", PORT_DNA);
|
|
|
|
perror("bind");
|
|
|
|
exit(-3);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-12-22 11:28:18 +00:00
|
|
|
extern int sigIoFlag;
|
|
|
|
extern int rhizome_server_socket;
|
2011-08-08 14:41:46 +00:00
|
|
|
int simpleServerMode()
|
|
|
|
{
|
|
|
|
while(1) {
|
2011-08-12 19:34:38 +00:00
|
|
|
struct sockaddr recvaddr;
|
2011-08-08 14:41:46 +00:00
|
|
|
socklen_t recvaddrlen=sizeof(recvaddr);
|
2011-12-22 11:28:18 +00:00
|
|
|
struct pollfd fds[128];
|
|
|
|
int fdcount;
|
2011-08-08 14:41:46 +00:00
|
|
|
int len;
|
2011-12-22 11:28:18 +00:00
|
|
|
int r;
|
2011-08-08 14:41:46 +00:00
|
|
|
|
2012-03-04 22:57:31 +00:00
|
|
|
if (servalShutdown) servalShutdownCleanly();
|
|
|
|
|
2011-08-08 14:41:46 +00:00
|
|
|
bzero((void *)&recvaddr,sizeof(recvaddr));
|
|
|
|
|
2011-12-22 11:28:18 +00:00
|
|
|
/* Get rhizome server started BEFORE populating fd list so that
|
|
|
|
the server's listen socket is in the list for poll() */
|
|
|
|
if (rhizome_datastore_path) rhizome_server_poll();
|
|
|
|
|
|
|
|
/* Get list of file descripters to watch */
|
|
|
|
fds[0].fd=sock; fds[0].events=POLLIN;
|
|
|
|
fdcount=1;
|
|
|
|
rhizome_server_get_fds(fds,&fdcount,128);
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_IO) {
|
2011-12-22 17:55:18 +00:00
|
|
|
printf("poll()ing file descriptors:");
|
|
|
|
{ int i;
|
|
|
|
for(i=0;i<fdcount;i++) { printf(" %d",fds[i].fd); } }
|
|
|
|
printf("\n");
|
|
|
|
}
|
2011-12-22 11:28:18 +00:00
|
|
|
|
|
|
|
/* Wait patiently for packets to arrive. */
|
|
|
|
if (rhizome_datastore_path) rhizome_server_poll();
|
|
|
|
while ((r=poll(fds,fdcount,100000))<1) {
|
|
|
|
if (sigIoFlag) { sigIoFlag=0; break; }
|
|
|
|
sleep(0);
|
2011-08-08 14:41:46 +00:00
|
|
|
}
|
2011-12-22 11:28:18 +00:00
|
|
|
if (rhizome_datastore_path) rhizome_server_poll();
|
|
|
|
|
2012-02-05 05:45:19 +00:00
|
|
|
unsigned char buffer[16384];
|
|
|
|
int ttl=-1; // unknown
|
|
|
|
|
2011-12-22 11:28:18 +00:00
|
|
|
if (fds[0].revents&POLLIN) {
|
|
|
|
|
2012-02-05 05:45:19 +00:00
|
|
|
len=recvwithttl(sock,buffer,sizeof(buffer),&ttl,&recvaddr,&recvaddrlen);
|
|
|
|
|
|
|
|
|
2011-12-22 11:28:18 +00:00
|
|
|
client_port=((struct sockaddr_in*)&recvaddr)->sin_port;
|
|
|
|
client_addr=((struct sockaddr_in*)&recvaddr)->sin_addr;
|
|
|
|
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_DNAREQUESTS) fprintf(stderr,"Received packet from %s:%d (len=%d).\n",inet_ntoa(client_addr),client_port,len);
|
|
|
|
if (debug&DEBUG_PACKETXFER) dump("recvaddr",(unsigned char *)&recvaddr,recvaddrlen);
|
|
|
|
if (debug&DEBUG_PACKETXFER) dump("packet",(unsigned char *)buffer,len);
|
2011-12-22 11:28:18 +00:00
|
|
|
if (dropPacketP(len)) {
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_SIMULATION) fprintf(stderr,"Simulation mode: Dropped packet due to simulated link parameters.\n");
|
2011-12-22 11:28:18 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Simple server mode doesn't really use interface numbers, so lie and say interface -1 */
|
2012-02-05 05:45:19 +00:00
|
|
|
if (packetOk(-1,buffer,len,NULL,ttl,&recvaddr,recvaddrlen,1)) {
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_PACKETFORMATS) setReason("Ignoring invalid packet");
|
2011-12-22 11:28:18 +00:00
|
|
|
}
|
2012-01-10 05:26:40 +00:00
|
|
|
if (debug&DEBUG_PACKETXFER) fprintf(stderr,"Finished processing packet, waiting for next one.\n");
|
2011-08-08 14:41:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2012-02-15 13:08:23 +00:00
|
|
|
|
|
|
|
#ifdef DEBUG_MEM_ABUSE
|
|
|
|
unsigned char groundzero[65536];
|
2012-02-15 13:21:12 +00:00
|
|
|
int memabuseInitP=0;
|
2012-02-15 13:08:23 +00:00
|
|
|
|
|
|
|
int memabuseInit()
|
|
|
|
{
|
2012-02-15 13:21:12 +00:00
|
|
|
if (memabuseInitP) {
|
|
|
|
fprintf(stderr,"WARNING: memabuseInit() called more than once.\n");
|
|
|
|
return memabuseCheck();
|
|
|
|
}
|
|
|
|
|
2012-02-15 13:08:23 +00:00
|
|
|
unsigned char *zero=(unsigned char *)0;
|
|
|
|
int i;
|
2012-02-15 13:21:12 +00:00
|
|
|
for(i=0;i<65536;i++) {
|
|
|
|
groundzero[i]=zero[i];
|
|
|
|
printf("%04x\n",i);
|
|
|
|
}
|
|
|
|
memabuseInitP=1;
|
2012-02-15 13:08:23 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-02-15 13:21:12 +00:00
|
|
|
int _memabuseCheck(const char *func,const char *file,const int line)
|
2012-02-15 13:08:23 +00:00
|
|
|
{
|
|
|
|
unsigned char *zero=(unsigned char *)0;
|
|
|
|
int firstAddr=-1;
|
|
|
|
int lastAddr=-1;
|
|
|
|
int i;
|
|
|
|
for(i=0;i<65536;i++) if (groundzero[i]!=zero[i]) {
|
|
|
|
lastAddr=i;
|
|
|
|
if (firstAddr==-1) firstAddr=i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastAddr>0) {
|
|
|
|
fprintf(stderr,"WARNING: Memory corruption in first 64KB of RAM detected.\n");
|
|
|
|
fprintf(stderr," Changed bytes exist in range 0x%04x - 0x%04x\n",firstAddr,lastAddr);
|
|
|
|
dump("Changed memory content",&zero[firstAddr],lastAddr-firstAddr+1);
|
|
|
|
dump("Initial memory content",&groundzero[firstAddr],lastAddr-firstAddr+1);
|
|
|
|
sleep(1);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr,"All's well at %s() %s:%d\n",func,file,line);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|