chiark / gitweb /
File descriptor passing.
[mLib] / ident.c
1 /* -*-c-*-
2  *
3  * $Id: ident.c,v 1.5 2002/01/13 13:33:15 mdw Exp $
4  *
5  * Nonblocking RFC931 client
6  *
7  * (c) 1999 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the mLib utilities library.
13  *
14  * mLib is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  * 
19  * mLib is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  * 
24  * You should have received a copy of the GNU Library General Public
25  * License along with mLib; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Revision history --------------------------------------------------* 
31  *
32  * $Log: ident.c,v $
33  * Revision 1.5  2002/01/13 13:33:15  mdw
34  * Track interface change for @lbuf@.
35  *
36  * Revision 1.4  2001/06/22 19:35:39  mdw
37  * New @conn_init@ interface.
38  *
39  * Revision 1.3  2000/10/08 11:17:45  mdw
40  * (ident_socket): change sizes to be @size_t@.
41  *
42  * Revision 1.2  2000/06/17 10:38:35  mdw
43  * Track changes to selbuf interface.
44  *
45  * Revision 1.1  1999/10/04 21:41:58  mdw
46  * Added ident client from `fw'.
47  *
48  */
49
50 /*----- Header files ------------------------------------------------------*/
51
52 #include <ctype.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56
57 #include <sys/types.h>
58 #include <unistd.h>
59
60 #include <sys/socket.h>
61 #include <netinet/in.h>
62 #include <arpa/inet.h>
63 #include <netdb.h>
64
65 #include "conn.h"
66 #include "dstr.h"
67 #include "exc.h"
68 #include "ident.h"
69 #include "selbuf.h"
70
71 /*----- Main code ---------------------------------------------------------*/
72
73 /* --- @next@ --- *
74  *
75  * Arguments:   @char **pp@ = address of string pointer
76  *
77  * Returns:     Address of next token.
78  *
79  * Use:         Reads the next token from the result string.  Tokens are
80  *              terminated by whitespace, or `:' or `,' characters.  The
81  *              result string has had `\' escapes removed; it's stored in
82  *              the same memory as the input string.  The actual content
83  *              of the delimiter doesn't seem to be interesting, so it
84  *              gets thrown away.
85  */
86
87 static char *next(char **pp)
88 {
89   char *p, *q, *r;
90
91   /* --- Deal with reads past the end of the string --- */
92
93   if (!*pp)
94     return ("");
95
96   /* --- Initialize various pointers into the string --- */
97
98   p = q = r = *pp;
99
100   /* --- Skip past any leading whitespace --- */
101
102   while (isspace((unsigned char)*p))
103     p++;
104
105   /* --- Now start work on the string itself --- */
106
107   for (;;) {
108     if (*p == 0 || *p == ':' || *p == ',' || isspace((unsigned char)*p))
109       break;
110     else if (*p == '\\') {
111       p++;
112       if (!*p) {
113         *q++ = '\\';
114         break;
115       }
116     }
117     *q++ = *p++;
118   }
119
120   /* --- Tidy up afterwards --- */
121
122   while (isspace((unsigned char)*p))
123     p++;
124   if (*p == 0)
125     *pp = 0;
126   else if (*p == ':' || *p == ',')
127     *pp = p + 1;
128   else
129     *pp = p;
130   *q = 0;
131
132   return (r);
133 }
134
135 /* --- @parse@ --- *
136  *
137  * Arguments:   @char *p@ = pointer to input string from identd
138  *              @ident_reply *i@ = pointer to output block
139  *
140  * Returns:     ---
141  *
142  * Use:         Parses a result string from an RFC931 (identd) server.
143  */
144
145 static void parse(char *p, ident_reply *i)
146 {
147   char *q;
148
149   /* --- Read the source and destination port numbers --- */
150
151   i->sport = atoi(next(&p));
152   i->dport = atoi(next(&p));
153
154   /* --- Find out what sort of a reply this is --- */
155
156   q = next(&p);
157   if (strcmp(q, "USERID") == 0) {
158     i->type = IDENT_USERID;
159     i->u.userid.os = next(&p);
160     i->u.userid.user = next(&p);
161   } else if (strcmp(q, "ERROR") == 0) {
162     i->type = IDENT_ERROR;
163     i->u.error = next(&p);
164   } else
165     i->type = IDENT_BAD;
166 }
167
168 /* --- @line@ --- *
169  *
170  * Arguments:   @char *s@ = pointer to string from ident server
171  *              @size_t len@ = length of the line
172  *              @void *p@ = pointer to my request block
173  *
174  * Returns:     ---
175  *
176  * Use:         Handles a string from an ident server.
177  */
178
179 static void line(char *s, size_t len, void *p)
180 {
181   ident_request *rq = p;
182
183   rq->state = IDENT_DONE;
184   close(rq->b.reader.fd);
185   if (!s)
186     rq->func(0, rq->p);
187   else {
188     ident_reply i;
189     parse(s, &i);
190     rq->func(&i, rq->p);
191   }
192   selbuf_destroy(&rq->b);
193 }
194
195 /* --- @connected@ --- *
196  *
197  * Arguments:   @int fd@ = file descriptor
198  *              @void *p@ = pointer to request block
199  *
200  * Returns:     ---
201  *
202  * Use:         Handles a connection to a remote ident server.
203  */
204
205 static void connected(int fd, void *p)
206 {
207   ident_request *rq = p;
208   dstr d = DSTR_INIT;
209
210   /* --- Handle an error during the connect --- */
211
212   if (fd < 0)
213     goto fail_0;
214
215   /* --- Initialize the string to send to the remote host --- */
216
217   TRY {
218     dstr_putf(&d, "%u, %u\r\n",
219               ntohs(rq->remote.sin_port), ntohs(rq->local.sin_port));
220   } CATCH switch (exc_type) {
221     case EXC_NOMEM:
222       EXIT_TRY;
223       goto fail_1;
224     default:
225       RETHROW;
226   } END_TRY;
227
228   /* --- Do the rest of the work --- */
229
230   if (write(fd, d.buf, d.len) < d.len)
231     goto fail_1;
232   dstr_destroy(&d);
233   rq->state = IDENT_READ;
234   selbuf_init(&rq->b, rq->s, fd, line, rq);
235   return;
236
237   /* --- Tidy up after misfortunes --- */
238
239 fail_1:
240   dstr_destroy(&d);
241   close(fd);
242 fail_0:
243   rq->state = IDENT_DONE;
244   rq->func(0, rq->p);
245 }
246
247 /* --- @ident_abort@ --- *
248  *
249  * Arguments:   @ident_request *rq@ = pointer to request block
250  *
251  * Returns:     ---
252  *
253  * Use:         Cancels an ident request in progress.
254  */
255
256 void ident_abort(ident_request *rq)
257 {
258   switch (rq->state) {
259     case IDENT_CONN:
260       conn_kill(&rq->c);
261       break;
262     case IDENT_READ:
263       close(rq->b.reader.fd);
264       selbuf_destroy(&rq->b);
265       break;
266   }
267 }
268
269 /* --- @go@ --- *
270  *
271  * Arguments:   @ident_request *rq@ = pointer to request block
272  *
273  * Returns:     ---
274  *
275  * Use:         Starts a connection to the remote ident server.
276  */
277
278 static void go(ident_request *rq)
279 {
280   int fd;
281   struct sockaddr_in sin;
282   int opt;
283
284   /* --- Create the socket I'll use --- */
285   
286   if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
287     goto fail_0;
288   memset(&sin, 0, sizeof(sin));
289   sin.sin_family = AF_INET;
290   sin.sin_port = 0;
291   sin.sin_addr = rq->local.sin_addr;
292   if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
293     goto fail_1;
294
295   /* --- Out-of-band data would confuse us --- */
296
297   opt = 1;
298   setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
299
300   /* --- Start a connection to the remote server --- */
301
302   sin.sin_family = AF_INET;
303   sin.sin_port = htons(113);
304   sin.sin_addr = rq->remote.sin_addr;
305   if (conn_init(&rq->c, rq->s, fd, (struct sockaddr *)&sin, sizeof(sin),
306                 connected, rq))
307     goto fail_1;
308
309   /* --- Finish off initializing the block --- */
310
311   rq->state = IDENT_CONN;
312   return;
313
314   /* --- Tidy up after lossage --- */
315
316 fail_1:
317   close(fd);
318 fail_0:
319   rq->state = IDENT_DONE;
320   rq->func(0, rq->p);
321 }
322
323 /* --- @ident@ --- *
324  *
325  * Arguments:   @ident_request *rq@ = pointer to request block
326  *              @sel_state *s@ = I/O multiplexor
327  *              @const struct sockaddr_in *local, *remote@ = addresses
328  *              @void (*func)(ident_reply *i, void *p)@ = handler function
329  *              @void *p@ = argument for handler
330  *
331  * Returns:     ---
332  *
333  * Use:         Initializes an ident request.
334  */
335
336 void ident(ident_request *rq, sel_state *s,
337            const struct sockaddr_in *local,
338            const struct sockaddr_in *remote, 
339            void (*func)(ident_reply */*i*/, void */*p*/),
340            void *p)
341 {
342   memcpy(&rq->local, local, sizeof(rq->local));
343   memcpy(&rq->remote, remote, sizeof(rq->remote));
344   rq->func = func;
345   rq->p = p;
346   rq->s = s;
347   go(rq);
348 }
349
350 /* --- @ident_socket@ --- *
351  *
352  * Arguments:   @ident_request *rq@ = pointer to request block
353  *              @sel_state *s@ = I/O multiplexor
354  *              @int sk@ = connected socket file descriptor
355  *              @void (*func)(ident_reply *i, void *p)@ = handler function
356  *              @void *p@ = argument for handler
357  *
358  * Returns:     ---
359  *
360  * Use:         An alternative interface to @ident@.  Initializes an ident
361  *              request from a connected socket, rather than from an explicit
362  *              address.  This will call @getsockname@ and @getpeername@ to
363  *              find out what the socket is actually connected to, which adds
364  *              convenience but wastes time.
365  */
366
367 void ident_socket(ident_request *rq, sel_state *s, int sk,
368                   void (*func)(ident_reply */*i*/, void */*p*/),
369                   void *p)
370 {
371   size_t sinsz;
372   if ((sinsz = sizeof(struct sockaddr_in),
373        getsockname(sk, (struct sockaddr *)&rq->local, &sinsz)) ||
374       (sinsz = sizeof(struct sockaddr_in),
375        getpeername(sk, (struct sockaddr *)&rq->remote, &sinsz))) {
376     rq->state = IDENT_DONE;
377     func(0, p);
378     return;
379   }
380
381   rq->func = func;
382   rq->p = p;
383   rq->s = s;
384   go(rq);
385 }
386
387 /*----- That's all, folks -------------------------------------------------*/