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