chiark / gitweb /
ce4ba29193bf09a963993839604239a54e34693b
[yaid] / ident.c
1 /* -*-c-*-
2  *
3  * Discover the owner of a connection
4  *
5  * (c) 2012 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Yet Another Ident Daemon (YAID).
11  *
12  * YAID is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * YAID is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with YAID; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #include <ctype.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <string.h>
34
35 #include <sys/types.h>
36 #include <unistd.h>
37
38 #include <arpa/inet.h>
39
40 #include <syslog.h>
41
42 #include <mLib/dstr.h>
43
44 /*----- Data structures ---------------------------------------------------*/
45
46 union addr {
47   struct in_addr ipv4;
48   struct in6_addr ipv6;
49 };
50
51 struct socket {
52   union addr addr;
53   int port;
54 };
55
56 enum { L, R, NDIR };
57
58 struct query {
59   int af;
60   struct socket s[NDIR];
61 } query;
62
63 #define RESPONSE(_)                                                     \
64   _(ERROR, U(error, unsigned))                                          \
65   _(UID, U(uid, uid_t))                                                 \
66   _(NAT, U(nat, struct socket))
67
68 #define ERROR(_)                                                        \
69   _(INVPORT, "INVALID-PORT")                                            \
70   _(NOUSER, "NO-USER")                                                  \
71   _(HIDDEN, "HIDDEN-USER")                                              \
72   _(UNKNOWN, "UNKNOWN-ERROR")
73
74 enum {
75 #define DEFENUM(err, tok) E_##err,
76   ERROR(DEFENUM)
77 #undef DEFENUM
78   E_LIMIT
79 };
80
81 enum {
82 #define DEFENUM(what, branch) R_##what,
83   RESPONSE(DEFENUM)
84 #undef DEFENUM
85   R_LIMIT
86 };
87
88 struct response {
89   unsigned what;
90   union {
91 #define DEFBRANCH(WHAT, branch) branch
92 #define U(memb, ty) ty memb;
93 #define N
94     RESPONSE(DEFBRANCH)
95 #undef U
96 #undef N
97 #undef DEFBRANCH
98   } u;
99 };
100
101 /*----- Static variables --------------------------------------------------*/
102
103 static const char *errtok[] = {
104 #define DEFTOK(err, tok) tok,
105   ERROR(DEFTOK)
106 #undef DEFTOK
107 };
108
109 static int parseaddr4(char **pp, union addr *a)
110   { a->ipv4.s_addr = strtoul(*pp, (char **)pp, 16); return (0); }
111
112 static int addreq4(const union addr *a, const union addr *aa)
113   { return a->ipv4.s_addr == aa->ipv4.s_addr; }
114
115 static const struct addrfamily {
116   int af;
117   const char *procfile;
118   int (*parseaddr)(char **pp, union addr *a);
119   int (*addreq)(const union addr *a, const union addr *aa);
120 } addrfamilytab[] = {
121   { AF_INET, "/proc/net/tcp", parseaddr4, addreq4 },
122   { AF_INET6, "/proc/net/tcp6", /*parseaddr6*/ },
123   { -1 }
124 };
125
126 /*----- Main code ---------------------------------------------------------*/
127
128 static void dputsock(dstr *d, int af, const struct socket *s)
129 {
130   char buf[INET6_ADDRSTRLEN];
131
132   inet_ntop(af, &s->addr, buf, sizeof(buf));
133   if (af != AF_INET6) dstr_puts(d, buf);
134   else { dstr_putc(d, '['); dstr_puts(d, buf); dstr_putc(d, ']'); }
135   dstr_putf(d, ":%d", s->port);
136 }
137
138 static void logmsg(const struct query *q, int prio, const char *msg, ...)
139 {
140   va_list ap;
141   dstr d = DSTR_INIT;
142
143   va_start(ap, msg);
144   dputsock(&d, q->af, &q->s[L]);
145   dstr_puts(&d, " <-> ");
146   dputsock(&d, q->af, &q->s[R]);
147   dstr_puts(&d, ": ");
148   dstr_vputf(&d, msg, &ap);
149   va_end(ap);
150   fprintf(stderr, "yaid: %s\n", d.buf);
151   dstr_destroy(&d);
152 }
153
154 static int sockeq(const struct addrfamily *af,
155                   const struct socket *sa, const struct socket *sb)
156   { return (af->addreq(&sa->addr, &sb->addr) && sa->port == sb->port); }
157
158 void identify(const struct query *q, struct response *r)
159 {
160   const struct addrfamily *af;
161   FILE *fp = 0;
162   dstr d = DSTR_INIT;
163   char *p, *pp;
164   struct socket s[4];
165   int i;
166   unsigned fl;
167 #define F_SADDR 1u
168 #define F_SPORT 2u
169 #define F_DADDR 4u
170 #define F_DPORT 8u
171 #define F_ALL (F_SADDR | F_SPORT | F_DADDR | F_DPORT)
172 #define F_ESTAB 16u
173   uid_t uid;
174   enum { LOC, REM, ST, UID, NFIELD };
175   int f, ff[NFIELD];
176
177   for (af = addrfamilytab; af->af >= 0; af++)
178     if (af->af == q->af) goto found_af;
179   logmsg(q, LOG_ERR, "unexpected address family `%d'", q->af);
180   goto err_unk;
181 found_af:;
182
183   if ((fp = fopen(af->procfile, "r")) == 0) {
184     logmsg(q, LOG_ERR, "failed to open `%s' for reading: %s",
185            af->procfile, strerror(errno));
186     goto err_unk;
187   }
188
189 #define NEXTFIELD do {                                                  \
190   for (p = pp; isspace((unsigned char)*p); p++);                        \
191   for (pp = p; *pp && !isspace((unsigned char)*pp); pp++);              \
192   if (*pp) *pp++ = 0;                                                   \
193 } while (0)
194
195   if (dstr_putline(&d, fp) == EOF) {
196     logmsg(q, LOG_ERR, "failed to read header line from `%s': %s",
197            af->procfile, ferror(fp) ? strerror(errno) : "unexpected EOF");
198     goto err_unk;
199   }
200
201   for (i = 0; i < NFIELD; i++) ff[i] = -1;
202   pp = d.buf;
203   for (f = 0;; f++) {
204     NEXTFIELD; if (!*p) break;
205     if (strcmp(p, "local_address") == 0)
206       ff[LOC] = f;
207     else if (strcmp(p, "rem_address") == 0 ||
208              strcmp(p, "remote_address") == 0)
209       ff[REM] = f;
210     else if (strcmp(p, "uid") == 0)
211       ff[UID] = f;
212     else if (strcmp(p, "st") == 0)
213       ff[ST] = f;
214     else if (strcmp(p, "rx_queue") == 0 ||
215              strcmp(p, "tm->when") == 0)
216       f--;
217   }
218   for (i = 0; i < NFIELD; i++) {
219     if (ff[i] < 0) {
220       logmsg(q, LOG_ERR, "failed to find required fields in `%s'",
221              af->procfile);
222       goto err_unk;
223     }
224   }
225
226   for (;;) {
227     DRESET(&d);
228     if (dstr_putline(&d, fp) == EOF) break;
229     pp = d.buf;
230     uid = -1;
231     for (f = 0;; f++) {
232       NEXTFIELD; if (!*p) break;
233       if (f == ff[LOC]) { i = L; goto compare; }
234       else if (f == ff[REM]) { i = R; goto compare; }
235       else if (f == ff[UID]) uid = atoi(p);
236       else if (f == ff[ST]) {
237         if (strtol(p, 0, 16) != 1) goto next_row;
238       }
239       continue;
240
241     compare:
242       if (af->parseaddr(&p, &s[0].addr)) goto next_row;
243       if (*p != ':') break; p++;
244       s[0].port = strtoul(p, 0, 16);
245       /* FIXME: accept forwarded queries from NAT */
246       if (!sockeq(af, &q->s[i], &s[0])) goto next_row;
247       else continue;
248     }
249     if (uid != -1) {
250       r->what = R_UID;
251       r->u.uid = uid;
252       goto done;
253     }
254   next_row:;
255   }
256
257   if (ferror(fp)) {
258     logmsg(q, LOG_ERR, "failed to read connection table: %s",
259            strerror(errno));
260     goto err_unk;
261   }
262
263   if (q->af == AF_INET) {
264     fclose(fp);
265     if ((fp = fopen("/proc/net/ip_conntrack", "r")) == 0) {
266       if (errno == ENOENT)
267         goto err_nouser;
268       else {
269         logmsg(q, LOG_ERR,
270                "failed to open `/proc/net/ip_conntrack' for reading: %s",
271                strerror(errno));
272         goto err_unk;
273       }
274     }
275
276     for (;;) {
277       DRESET(&d);
278       if (dstr_putline(&d, fp) == EOF) break;
279       pp = d.buf;
280       NEXTFIELD; if (!*p) break;
281       if (strcmp(p, "tcp") != 0) continue;
282       i = 0;
283       fl = 0;
284       for (;;) {
285         NEXTFIELD; if (!*p) break;
286         if (strcmp(p, "ESTABLISHED") == 0)
287           fl |= F_ESTAB;
288         else if (strncmp(p, "src=", 4) == 0) {
289           inet_pton(AF_INET, p + 4, &s[i].addr);
290           fl |= F_SADDR;
291         } else if (strncmp(p, "dst=", 4) == 0) {
292           inet_pton(AF_INET, p + 4, &s[i + 1].addr);
293           fl |= F_DADDR;
294         } else if (strncmp(p, "sport=", 6) == 0) {
295           s[i].port = atoi(p + 6);
296           fl |= F_SPORT;
297         } else if (strncmp(p, "dport=", 6) == 0) {
298           s[i + 1].port = atoi(p + 6);
299           fl |= F_DPORT;
300         }
301         if ((fl & F_ALL) == F_ALL) {
302           fl &= ~F_ALL;
303           if (i < 4) i += 2;
304           else break;
305         }
306       }
307
308 #ifdef notdef
309       {
310         dstr dd = DSTR_INIT;
311         dstr_putf(&dd, "%sestab ", (fl & F_ESTAB) ? " " : "!");
312         dputsock(&dd, af->af, &s[0]);
313         dstr_puts(&dd, "<->");
314         dputsock(&dd, af->af, &s[1]);
315         dstr_puts(&dd, " | ");
316         dputsock(&dd, af->af, &s[2]);
317         dstr_puts(&dd, "<->");
318         dputsock(&dd, af->af, &s[3]);
319         printf("parsed: %s\n", dd.buf);
320         dstr_destroy(&dd);
321       }
322 #endif
323
324       if (!(fl & F_ESTAB)) continue;
325
326       for (i = 0; i < 4; i++)
327         if (sockeq(af, &s[i], &q->s[L])) goto found_local;
328       continue;
329       putchar('.');
330     found_local:
331       if (!sockeq(af, &s[i^1], &s[i^2]) ||
332           !sockeq(af, &s[i^1], &q->s[R]))
333         continue;
334       r->what = R_NAT;
335       r->u.nat = s[i^3];
336       goto done;
337     }
338
339     if (ferror(fp)) {
340       logmsg(q, LOG_ERR, "failed to read `/proc/net/ip_conntrack': %s",
341              strerror(errno));
342       goto err_unk;
343     }
344   }
345
346 #undef NEXTFIELD
347
348 err_nouser:
349   r->what = R_ERROR;
350   r->u.error = E_NOUSER;
351   goto done;
352 err_unk:
353   r->what = R_ERROR;
354   r->u.error = E_UNKNOWN;
355 done:
356   dstr_destroy(&d);
357 }
358
359 /*----- That's all, folks -------------------------------------------------*/
360
361 int main(int argc, char *argv[])
362 {
363   struct query q;
364   struct response r;
365   char buf[INET6_ADDRSTRLEN];
366
367   q.af = AF_INET;
368   inet_pton(AF_INET, argv[1], &q.s[L].addr.ipv4);
369   q.s[L].port = atoi(argv[2]);
370   inet_pton(AF_INET, argv[3], &q.s[R].addr.ipv4);
371   q.s[R].port = atoi(argv[4]);
372
373   identify(&q, &r);
374
375   switch (r.what) {
376     case R_UID:
377       printf("uid %d\n", r.u.uid);
378       break;
379     case R_ERROR:
380       if (r.u.error < E_LIMIT) printf("error %s\n", errtok[r.u.error]);
381       else printf("error E%u\n", r.u.error);
382       break;
383     case R_NAT:
384       inet_ntop(q.af, &r.u.nat.addr, buf, sizeof(buf));
385       printf("nat -> %s:%d\n", buf, r.u.nat.port);
386       break;
387     default:
388       printf("unknown response\n");
389       break;
390   }
391
392   return (0);
393 }