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