chiark / gitweb /
add stuff from rules/binary to .gitignore
[inn-innduct.git] / nnrpd / line.c
1 /*  $Id: line.c 7837 2008-05-19 17:14:15Z iulius $
2 **
3 **  Line by line reading support from sockets/pipes
4 **
5 **  Written by Alex Kiernan (alex.kiernan@thus.net)
6 **
7 **  This code implements a infinitely (well size_t) long single line
8 **  read routine, to protect against eating all available memory it
9 **  actually starts discarding characters if you try to send more than
10 **  the maximum article size in a single line.
11 ** 
12 */
13
14 #include "config.h"
15 #include "clibrary.h"
16 #include <assert.h>
17 #ifdef HAVE_SYS_SELECT_H
18 # include <sys/select.h>
19 #endif
20
21 #include "inn/messages.h"
22 #include "nnrpd.h"
23
24 #ifdef HAVE_SSL
25 #include <openssl/ssl.h>
26 #include <signal.h>
27 extern SSL *tls_conn;
28 #endif
29
30 /*
31 **  free a previously allocated line structure
32 */
33 void
34 line_free(struct line *line)
35 {
36     static const struct line nullline = {0, 0, 0, 0};
37
38     if (line && line->start) {
39         free(line->start);
40         *line = nullline;
41     }
42 }
43
44 #ifdef HAVE_SSL
45 /*
46 **  Alarm signal handler for client timeout.
47 */
48 static void
49 alarmHandler(int s)
50 {
51     SSL_shutdown(tls_conn);
52     tls_conn = NULL;
53     errno = ECONNRESET;
54 }
55 #endif
56   
57 /*
58 **  initialise a new line structure
59 */
60 void
61 line_init(struct line *line)
62 {
63     assert(line);
64     line->allocated = NNTP_STRLEN;
65     line->where = line->start = xmalloc(line->allocated);
66     line->remaining = 0;
67 }
68
69 static ssize_t
70 line_doread(void *p, size_t len, int timeout)
71 {
72     ssize_t n;
73
74 #ifdef HAVE_SSL
75     if (tls_conn) {
76         int err;
77         xsignal(SIGALRM, alarmHandler);
78         do {
79             alarm(timeout);
80             n = SSL_read(tls_conn, p, len);
81             alarm(0);
82             if (tls_conn == NULL) {
83                 break;
84         }
85             err = SSL_get_error(tls_conn, n);
86             switch (err) {
87             case SSL_ERROR_SYSCALL:
88                 break;
89
90             case SSL_ERROR_SSL:
91                 SSL_shutdown(tls_conn);
92                 tls_conn = NULL;
93                 errno = ECONNRESET;
94                 break;
95             }
96         } while (err == SSL_ERROR_WANT_READ);
97         xsignal(SIGALRM, SIG_DFL);
98     } else {
99 #endif
100         do {
101             n = read(STDIN_FILENO, p, len);
102         } while (n == -1 && errno == EINTR);
103 #ifdef HAVE_SSL
104     }
105 #endif
106     return n;
107 }
108
109 READTYPE
110 line_read(struct line *line, int timeout, const char **p, size_t *len)
111 {
112     char *where;
113     char *lf = NULL;
114     READTYPE r = RTok;
115
116     assert(line != NULL);
117     assert(line->start != NULL);
118     /* shuffle any trailing portion not yet processed to the start of
119      * the buffer */
120     if (line->remaining != 0) {
121         if (line->start != line->where) {
122             memmove(line->start, line->where, line->remaining);
123         }
124         lf = memchr(line->start, '\n', line->remaining);
125     }
126     where = line->start + line->remaining;
127
128     /* if we found a line terminator in the data we have we don't need
129      * to ask for any more */
130     if (lf == NULL) {
131         do {
132             fd_set rmask;
133             int i;
134             ssize_t count;
135
136             /* if we've filled the line buffer, double the size,
137              * reallocate the buffer and try again */
138             if (where == line->start + line->allocated) {
139                 size_t newsize = line->allocated * 2;
140             
141                 /* don't grow the buffer bigger than the maximum
142                  * article size we'll accept */
143                 if (PERMaccessconf->localmaxartsize > NNTP_STRLEN)
144                     if (newsize > (unsigned)PERMaccessconf->localmaxartsize)
145                         newsize = PERMaccessconf->localmaxartsize;
146
147                 /* if we're trying to grow from the same size, to the
148                  * same size, we must have hit the localmaxartsize
149                  * buffer for a second (or subsequent) time - the user
150                  * is likely trying to DOS us, so don't double the
151                  * size any more, just overwrite characters until they
152                  * stop, then discard the whole thing */
153                 if (newsize == line->allocated) {
154                     warn("%s overflowed our line buffer (%ld), "
155                          "discarding further input", ClientHost,
156                          PERMaccessconf->localmaxartsize);
157                     where = line->start;
158                     r = RTlong;
159                 } else {
160                     line->start = xrealloc(line->start, newsize);
161                     where = line->start + line->allocated;
162                     line->allocated = newsize;
163                 }
164             }
165
166 #ifdef HAVE_SSL
167             /* It seems that the SSL_read cannot be mixed with select()
168              * as in the current code.  SSL communicates in its own data
169              * blocks and hand shaking.  The do_readline using SSL_read
170              * could return, but still with a partial line in the SSL_read
171              * buffer.  Then the server SSL routine would sit there waiting
172              * for completion of that data block while nnrpd sat at the
173              * select() routine waiting for more data from the server.
174              *
175              * Here, we decide to just bypass the select() wait.  Unlike
176              * innd with multiple threads, the select on nnrpd is just
177              * waiting on a single file descriptor, so it is not really
178              * essential with blocked read like SSL_read.  Using an alarm
179              * signal around SSL_read for non active timeout, SSL works
180              * without dead locks.  However, without the select() wait,
181              * the IDLE timer stat won't be collected...
182              */
183             if (tls_conn == NULL) {
184 #endif
185                 /* Wait for activity on stdin, updating timer stats as we
186                  * go. */
187                 do {
188                     struct timeval t;
189
190                     FD_ZERO(&rmask);
191                     FD_SET(STDIN_FILENO, &rmask);
192                     t.tv_sec = timeout;
193                     t.tv_usec = 0;
194                     TMRstart(TMR_IDLE);
195                     i = select(STDIN_FILENO + 1, &rmask, NULL, NULL, &t);
196                     TMRstop(TMR_IDLE);
197                     if (i == -1 && errno != EINTR) {
198                         syswarn("%s can't select", ClientHost);
199                         return RTtimeout;
200                     }
201                 } while (i == -1);
202
203                 /* If stdin didn't select, we must have timed out. */
204                 if (i == 0 || !FD_ISSET(STDIN_FILENO, &rmask))
205                     return RTtimeout;
206 #ifdef HAVE_SSL
207             }
208 #endif
209             count = line_doread(where,
210                                 line->allocated - (where - line->start), 
211                                 timeout);
212
213             /* give timeout for read errors */
214             if (count < 0) {
215                 sysnotice("%s can't read", ClientHost);
216                 return RTtimeout;
217             }
218             /* if we hit EOF, terminate the string and send it back */
219             if (count == 0) {
220                 assert((where + count) < (line->start + line->allocated));
221                 where[count] = '\0';
222                 return RTeof;
223             }
224             /* search for `\n' in what we just read, if we find it we'll
225              * drop out and return the line for processing */
226             lf = memchr(where, '\n', count);
227             where += count;
228         } while (lf == NULL);
229     }
230
231     /* remember where we've processed up to so we can start off there
232      * next time */
233     line->where = lf + 1;
234     line->remaining = where - line->where;
235
236     if (r == RTok) {
237         /* if we see a full CRLF pair strip them both off before
238          * returning the line to our caller, if we just get an LF
239          * we'll accept that too */
240         if (lf > line->start && lf[-1] == '\r') {
241             --lf;
242         }
243         *lf = '\0';
244         *len = lf - line->start;
245         *p = line->start;
246     }
247     return r;
248 }