/* * Long-running daemon which fields packets from send.c, running an * external program or script for each call. It's a long-running * daemon rather than an inetd service in order to make it easier to * handle packets to both v4 and v6 addresses. * * The command gets info on stdin. This takes the form of lines whose * first character indicates what the rest of the line is: * * H Address from which the packet was sent, per * getnameinfo's NI_NUMERICHOST flag. * * P Port number from which the packet was sent, per * getnameinfo's NI_NUMERICSERV flag. * * I Caller-ID of the call. * * T The target (called) number. */ static const char * const listen_strings[] = { "98.124.61.90/37867", "2607:f2c0:fffd:1020::90/37867", 0 }; static const char * const sigkeyfile = "/home/mouse/phone-alert/key"; static const char * const devnull = "/dev/null"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct listen LISTEN; typedef struct recent RECENT; struct recent { RECENT *link; unsigned char id[7]; time_t expire; } ; struct listen { struct sockaddr *sa; socklen_t salen; int sock; const char *str; } ; static LISTEN *listens; static int nlisten; static int cmd_ac; static char **cmd_av; static char *sigkey; static int sigkeylen; static struct pollfd *pfdv; static struct sockaddr_storage from; static socklen_t fromlen; static char from_a_txt[NI_MAXHOST]; static char from_p_txt[NI_MAXSERV]; static unsigned char pktbuf[65536]; static int pktlen; static unsigned char xorbuf[65536]; static RECENT *recent; static void *deconst(const void *p) { void *dc; bcopy(&p,&dc,sizeof(void *)); return(dc); } static void fail(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); static void fail(const char *fmt, ...) { va_list ap; char *s; int l; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); write(2,s,l); write(2,"\n",1); exit(1); } static void handleargs(int ac, char **av) { if (ac < 2) fail("no command"); cmd_av = av + 1; cmd_ac = ac - 1; } static void setup(void) { struct stat stb; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int i; int e; int j; int s; i = open(sigkeyfile,O_RDONLY,0); if (i < 0) fail("%s: open: %s",sigkeyfile,strerror(errno)); if (fstat(i,&stb) < 0) { /* can this happen? maybe EIO.... */ fail("fstat %s: %s",sigkeyfile,strerror(errno)); } if (stb.st_size > 65536) { sigkeylen = 65536; } else { sigkeylen = stb.st_size; } if (sigkeylen < 4) fail("%s: too short (%d)",sigkeyfile,sigkeylen); sigkey = malloc(sigkeylen); j = read(i,sigkey,sigkeylen); if (j < 0) fail("%s: read: %s",sigkeyfile,strerror(errno)); if (j != sigkeylen) fail("%s: read: got %d, expected %d",sigkeyfile,j,sigkeylen); close(i); nlisten = 0; for (i=0;listen_strings[i];i++) { char *hs; char *ps; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST | AI_NUMERICSERV; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; hs = strdup(listen_strings[i]); ps = index(hs,'/'); if (! ps) fail("no / in `%s'",hs); *ps++ = '\0'; ai0 = 0; e = getaddrinfo(hs,ps,&hints,&ai0); free(hs); if (e) fail("looking up %s: %s",listen_strings[i],gai_strerror(e)); for (ai=ai0;ai;ai=ai->ai_next) { j = nlisten ++; listens = realloc(listens,nlisten*sizeof(*listens)); listens[j].sa = malloc(ai->ai_addrlen); listens[j].salen = ai->ai_addrlen; bcopy(ai->ai_addr,listens[j].sa,ai->ai_addrlen); s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) fail("%s: socket: %s",listen_strings[i],strerror(errno)); if (bind(s,ai->ai_addr,ai->ai_addrlen) < 0) fail("%s: bind: %s",listen_strings[i],strerror(errno)); listens[j].sock = s; listens[j].str = listen_strings[i]; fcntl(s,F_SETFD,FD_CLOEXEC); } freeaddrinfo(ai0); } if (nlisten < 1) fail("no listening points"); pfdv = malloc(nlisten*sizeof(struct pollfd)); recent = 0; i = open(devnull,O_RDWR,0); if (i < 0) fail("%s: %s",devnull,strerror(errno)); while (i < 3) { i = dup(i); if (i < 0) fail("dup: %s",strerror(errno)); } close(i); } static void do_xor(int len, const unsigned char *src, unsigned char xb) { int i; for (i=len-1;i>=0;i--) xorbuf[i] = src[i] ^ xb; } static int recvpkt(void) { int i; int e; for (i=nlisten-1;i>=0;i--) { pfdv[i].fd = listens[i].sock; pfdv[i].events = POLLIN | POLLRDNORM; } i = poll(pfdv,nlisten,INFTIM); if (i < 0) { switch (errno) { case EINTR: return(0); break; } fail("poll: %s",strerror(errno)); } for (i=nlisten-1;i>=0;i--) { if (pfdv[i].revents & (POLLIN | POLLRDNORM | POLLERR | POLLHUP | POLLNVAL)) { fromlen = sizeof(from); pktlen = recvfrom(listens[i].sock,&pktbuf[0],sizeof(pktbuf),0,(void *)&from,&fromlen); if (pktlen < 0) { fprintf(stderr,"recvfrom %s: %s\n",listens[i].str,strerror(errno)); continue; } e = getnameinfo((void *)&from,fromlen,&from_a_txt[0],NI_MAXHOST,&from_p_txt[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_DGRAM); if (e) { fprintf(stderr,"getnameinfo (af %d): %s",from.ss_family,gai_strerror(e)); continue; } return(1); } } return(0); } static void gripefrom(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void gripefrom(const char *fmt, ...) { va_list ap; char *s; int l; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"packet from %s/%s: %s\n",&from_a_txt[0],&from_p_txt[0],s); free(s); } static FILE *run_input(void) { pid_t kid; int fp[2]; int dp[2]; int ip[2]; int op[2]; int ep[2]; int e; unsigned char eb[1+sizeof(int)]; int n; #define KE_FORK 1 #define KE_EXEC 2 void kid_fail(int reason, int err) { eb[0] = reason; bcopy(&err,&eb[1],sizeof(int)); send(fp[0],&eb[0],1+sizeof(int),0); exit(0); } do <"fail"> { if (pipe(&dp[0]) < 0) { fprintf(stderr,"pipe: %s\n",strerror(errno)); break <"fail">; } do <"fail"> { if (socketpair(AF_LOCAL,SOCK_STREAM,0,&ip[0]) < 0) { fprintf(stderr,"socketpair: %s\n",strerror(errno)); break <"fail">; } shutdown(ip[0],SHUT_WR); shutdown(ip[1],SHUT_RD); do <"fail"> { if (pipe(&op[0]) < 0) { fprintf(stderr,"pipe: %s\n",strerror(errno)); break <"fail">; } do <"fail"> { if (pipe(&ep[0]) < 0) { fprintf(stderr,"pipe: %s\n",strerror(errno)); break <"fail">; } do <"fail"> { if (socketpair(AF_LOCAL,SOCK_STREAM,0,&fp[0]) < 0) { fprintf(stderr,"socketpair: %s\n",strerror(errno)); break <"fail">; } do <"fail"> { fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"fork: %s\n",strerror(errno)); break <"fail">; } if (kid > 0) { wait(0); close(fp[0]); close(dp[0]); close(ip[0]); close(ip[1]); close(op[0]); close(op[1]); close(ep[0]); close(ep[1]); do <"fail"> { n = recv(fp[1],&eb[0],1+sizeof(int),MSG_WAITALL); if (n < 0) { fprintf(stderr,"fork pipe recv: %s\n",strerror(errno)); break <"fail">; } else if (n == 0) { FILE *rv; close(fp[1]); rv = fdopen(dp[1],"w"); if (! rv) close(dp[1]); return(rv); } else if (n != 1+sizeof(int)) { fprintf(stderr,"fork pipe strange recv (got %d not %d)\n",n,(int)(1+sizeof(int))); break <"fail">; } else { bcopy(&eb[1],&e,sizeof(int)); switch (eb[0]) { case KE_FORK: fprintf(stderr,"child fork: %s\n",strerror(e)); break; case KE_EXEC: fprintf(stderr,"command exec: %s\n",strerror(e)); break; default: fprintf(stderr,"broken fork pipe key %d: %s\n",eb[0],strerror(e)); break; } break <"fail">; } } while (0); close(fp[1]); close(dp[1]); return(0); } else { kid = fork(); if (kid < 0) { kid_fail(KE_FORK,errno); } if (kid > 0) exit(0); kid = fork(); if (kid < 0) { kid_fail(KE_FORK,errno); } if (kid > 0) { typedef struct eb EB; struct eb { char *b; int a; int l; } ; struct pollfd pfd[4]; int pn; EB i; EB o; EB e; int iract; int irpx; int iwact; int iwpx; int oact; int opx; int eact; int epx; int dbuf[256]; int dlen; int n; void eb_init(EB *b) { b->b = 0; b->a = 0; b->l = 0; } void eb_append(EB *b, const void *data, int len) { if (b->l+len > b->a) b->b = realloc(b->b,b->a=b->l+len); bcopy(data,b->b+b->l,len); b->l += len; } void eb_drophead(EB *b, int n) { if ((n < 0) || (n > b->l)) abort(); if (n == 0) return; if (n < b->l) bcopy(b->b+n,b->b,b->l-n); b->l -= n; } void eb_flush_lines(EB *b, void (*line)(const void *, int)) { void *nl; int n; n = 0; while (n < b->l) { nl = memchr(b->b+n,'\n',b->l-n); if (! nl) break; (*line)(b->b+n,1+((const char *)nl)-(b->b+n)); n = 1 + ((const char *)nl) - b->b; } if (n > 0) eb_drophead(b,n); } void getdata(int act, int px, int rfd, const char *kind, void (*got)(const void *, int)) { if (act && (pfd[px].revents & (POLLIN | POLLRDNORM | POLLERR | POLLHUP | POLLNVAL))) { dlen = read(rfd,&dbuf[0],sizeof(dbuf)); if (dlen < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: break; default: fprintf(stderr,"aux child %s read: %s\n",kind,strerror(errno)); exit(1); } } else if (dlen == 0) { close(rfd); (*got)(0,0); } else { (*got)(&dbuf[0],dlen); } } } void save_i(const void *data, int len) { if (! data) { iract = 0; return; } if (iwact) eb_append(&i,data,len); } int wr_fd(EB *b, const void *data, int len, const char *tag, int fd) { struct iovec iov[4]; int rv; void wr_line(const void *data, int len) { iov[0].iov_base = deconst("command "); iov[0].iov_len = strlen(iov[0].iov_base); iov[1].iov_base = deconst(tag); iov[1].iov_len = strlen(iov[1].iov_base); iov[2].iov_base = deconst(": "); iov[2].iov_len = strlen(iov[2].iov_base); iov[3].iov_base = deconst(data); iov[3].iov_len = len; writev(fd,&iov[0],4); } rv = 0; if (! data) { if (b->l == 0) return(1); data = "\n"; len = 1; rv = 1; } if ((b->l == 0) && (((const char *)data)[len-1] == '\n') && !memchr(data,'\n',len-1)) { wr_line(data,len); return(rv); } eb_append(b,data,len); eb_flush_lines(b,&wr_line); return(rv); } void wr_err(const void *data, int len) { if (wr_fd(&e,data,len,"stderr",2)) eact = 0; } void wr_out(const void *data, int len) { if (wr_fd(&o,data,len,"stdout",1)) oact = 0; } close(fp[0]); close(fp[1]); close(dp[1]); close(ip[0]); close(op[1]); close(ep[1]); eb_init(&i); eb_init(&o); eb_init(&e); fcntl(dp[0],F_SETFL,fcntl(dp[0],F_GETFL,0)|O_NONBLOCK); fcntl(ip[1],F_SETFL,fcntl(ip[1],F_GETFL,0)|O_NONBLOCK); fcntl(op[0],F_SETFL,fcntl(op[0],F_GETFL,0)|O_NONBLOCK); fcntl(ep[0],F_SETFL,fcntl(ep[0],F_GETFL,0)|O_NONBLOCK); iract = 1; iwact = 1; oact = 1; eact = 1; while (1) { pn = 0; if (iract) { pfd[pn].fd = dp[0]; pfd[pn].events = POLLIN | POLLRDNORM; irpx = pn++; } else if (iwact && (i.l < 1)) { close(ip[1]); iwact = 0; } if (iwact && (i.l > 0)) { pfd[pn].fd = ip[1]; pfd[pn].events = POLLOUT | POLLWRNORM; iwpx = pn++; } if (oact) { pfd[pn].fd = op[0]; pfd[pn].events = POLLIN | POLLRDNORM; opx = pn++; } if (eact) { pfd[pn].fd = ep[0]; pfd[pn].events = POLLIN | POLLRDNORM; epx = pn++; } if (pn == 0) exit(0); if (poll(&pfd[0],pn,INFTIM) < 0) { switch (errno) { case EINTR: continue; break; } fprintf(stderr,"aux child poll: %s\n",strerror(errno)); exit(1); } if (iwact && (i.l > 0) && (pfd[iwpx].revents & (POLLOUT | POLLWRNORM | POLLERR | POLLHUP | POLLNVAL))) { n = send(ip[1],i.b,i.l,MSG_NOSIGNAL); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: break; case EPIPE: iwact = 0; break; default: fprintf(stderr,"aux child input write: %s\n",strerror(errno)); exit(1); } } else if (n == 0) { fprintf(stderr,"aux child input zero write\n"); exit(1); } else if (n > i.l) { fprintf(stderr,"aux child input write overrun (%d > %d)\n",n,i.l); exit(1); } else { eb_drophead(&i,n); } } getdata(iract,irpx,dp[0],"input",&save_i); getdata(eact,epx,ep[0],"stderr",&wr_err); getdata(oact,opx,op[0],"stdout",&wr_out); } } else { fcntl(fp[0],F_SETFD,FD_CLOEXEC); close(fp[1]); close(dp[0]); close(dp[1]); close(ip[1]); dup2(ip[0],0); close(ip[0]); close(op[0]); dup2(op[1],1); close(op[1]); close(ep[0]); dup2(ep[1],2); close(ep[1]); execv(cmd_av[0],cmd_av); kid_fail(KE_EXEC,errno); } } } while (0); close(fp[0]); close(fp[1]); } while (0); close(ep[0]); close(ep[1]); } while (0); close(op[0]); close(op[1]); } while (0); close(ip[0]); close(ip[1]); } while (0); close(dp[0]); close(dp[1]); } while (0); return(0); } static void processpkt(void) { char *clid; int clidlen; char *ext; int extlen; unsigned char *hash; int hashlen; void *h; unsigned char hash1[32]; unsigned char sig[32]; RECENT *r; RECENT **rp; time_t now; FILE *f; if (pktlen < 1+7+1+1+32) { gripefrom("packet too short (%d)",pktlen); return; } clidlen = pktbuf[8]; extlen = pktbuf[9]; if (pktlen != 1+7+1+1+clidlen+extlen+32) { gripefrom("packet length wrong (1+7+1+1+%d+%d+32 != %d)",clidlen,extlen,pktlen); return; } clid = &pktbuf[10]; ext = clid + clidlen; hash = ext + extlen; hashlen = hash - (unsigned char *)&pktbuf[0]; h = sha256_init(); sha256_process_bytes(h,sigkey,sigkeylen); do_xor(hashlen,&pktbuf[0],0x6b); sha256_process_bytes(h,&xorbuf[0],hashlen); sha256_process_bytes(h,sigkey,sigkeylen); sha256_result(h,&hash1[0]); h = sha256_init(); sha256_process_bytes(h,sigkey,sigkeylen); sha256_process_bytes(h,&hash1[0],32); do_xor(hashlen,&pktbuf[0],0xc4); sha256_process_bytes(h,&xorbuf[0],hashlen); sha256_process_bytes(h,sigkey,sigkeylen); sha256_result(h,&sig[0]); if (bcmp(&sig[0],hash,32)) { gripefrom("signature wrong"); return; } time(&now); rp = &recent; while ((r = *rp)) { if (r->expire < now) { *rp = r->link; free(r); } else { if (! bcmp(&pktbuf[1],&r->id[0],7)) return; rp = &r->link; } } r = malloc(sizeof(RECENT)); bcopy(&pktbuf[1],&r->id[0],7); r->expire = now + 60; r->link = recent; recent = r; f = run_input(); if (f) { fprintf(f,"H%s\nP%s\nI%.*s\nT%.*s\n",&from_a_txt[0],&from_p_txt[0],clidlen,clid,extlen,ext); fclose(f); } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); setup(); while (1) { if (recvpkt()) processpkt(); } exit(0); }