1 /*
2 **
3 ** Copyright (c) 2010-2017 The SquirrelMail Project Team
4 ** Copyright (c) 2002-2010 Dave McMurtrie
5 **
6 ** Licensed under the GNU GPL. For full terms see the file COPYING.
7 **
8 ** This file is part of SquirrelMail IMAP Proxy.
9 **
10 ** Facility:
11 **
12 ** select.c
13 **
14 ** Abstract:
15 **
16 ** routines to support SELECT state caching in squirrelmail-imap_proxy.
17 **
18 ** Authors:
19 **
20 ** Dave McMurtrie <davemcmurtrie@hotmail.com>
21 **
22 ** Version:
23 **
24 ** $Id: select.c 14647 2017-01-27 20:53:57Z pdontthink $
25 **
26 ** Modification History:
27 **
28 ** $Log$
29 **
30 ** Revision 1.5 2009/10/16 14:22:38 dave64
31 ** Applied patch by Jose Luis Tallon to fix broken syslog call
32 **
33 ** Revision 1.4 2005/06/15 12:11:12 dgm
34 ** Remove unused variables.
35 **
36 ** Revision 1.3 2004/11/10 15:34:14 dgm
37 ** Explictly NULL terminate all strings that are the result of strncpy.
38 **
39 ** Revision 1.2 2004/03/11 15:14:06 dgm
40 ** Behavior of calling code when Populate_Select_Cache() fails
41 ** modified to not send error back to client.
42 ** "safe" command list updated.
43 **
44 ** Revision 1.1 2004/02/24 15:13:21 dgm
45 ** Initial revision
46 **
47 */
48
49 #define _REENTRANT
50
51 #include <syslog.h>
52 #include <string.h>
53 #include <stdio.h>
54 #include <errno.h>
55
56 #include "imapproxy.h"
57
58 /*
59 * External globals
60 */
61 extern int errno;
62 extern IMAPCounter_Struct *IMAPCount;
63
64 /*
65 * Internal prototypes
66 */
67 static int Send_Cached_Select_Response( ITD_Struct *, ISC_Struct *, char * );
68 static int Populate_Select_Cache( ITD_Struct *, ISC_Struct *, char *, char *, unsigned int );
69
70
71 /*
72 * Function definitions.
73 */
74
75 /*++
76 * Function: Handle_Select_Command
77 *
78 * Purpose: The client sent a SELECT command. Either hit the cache,
79 * or get data from the IMAP server.
80 *
81 * Parameters: ptr to ITD -- client transaction descriptor
82 * ptr to ITD -- server transaction descriptor
83 * ptr to ISC -- IMAP select cache structure
84 * ptr to char -- The select command string from the client.
85 * unsigned int -- the length of the select command
86 *
87 * Returns: 0 - The caller should consider the entire SELECT
88 * transaction to be complete. This return code does not
89 * imply successful completion. Rather, it implies that
90 * neither the client nor the server should be sending more
91 * data wrt this transaction.
92 *
93 * 1 - This return code implies that this function was not able
94 * to really accomplish anything useful. The caller should
95 * attempt to send the SELECT command by an alternate means.
96 * The SELECT command is still in the client read buffer and
97 * may be proxied directly to the server without the use of
98 * this function.
99 *
100 * -1 - A hard client failure condition. The client socket
101 * should be shut down.
102 *
103 * -2 - A hard server failure condition. The client and server
104 * sockets should both be shut down.
105 *
106 * Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
107 *
108 * Notes: The SELECT command string passed into here will be the
109 * entire command, including the tag.
110 *--
111 */
Handle_Select_Command(ITD_Struct * Client,ITD_Struct * Server,ISC_Struct * ISC,char * SelectCmd,int SelectCmdLength)112 extern int Handle_Select_Command( ITD_Struct *Client,
113 ITD_Struct *Server,
114 ISC_Struct *ISC,
115 char *SelectCmd,
116 int SelectCmdLength )
117 {
118 char *fn = "Handle_Select_Command";
119 char *Mailbox;
120 char *Tag;
121 char *CP;
122 int rc;
123
124 char Buf[ BUFSIZE ];
125
126 IMAPCount->TotalSelectCommands++;
127
128 /*
129 * Make a local copy of the select buffer so we can chop it to hell without
130 * offending the caller.
131 */
132 if ( SelectCmdLength >= BUFSIZE )
133 {
134 IMAPCount->SelectCacheMisses++;
135 syslog( LOG_ERR, "%s: Length of SELECT command (%d bytes) would overflow %d byte buffer.", fn, SelectCmdLength, BUFSIZE );
136 return( 1 );
137 }
138
139 memcpy( Buf, SelectCmd, SelectCmdLength );
140 Buf[ SelectCmdLength ] = '\0';
141
142 /*
143 * NULL terminate the buffer at the end of the mailbox name
144 */
145 CP = memchr( (const void *)Buf, '\r', SelectCmdLength );
146 if ( ! CP )
147 {
148 IMAPCount->SelectCacheMisses++;
149
150 syslog( LOG_ERR, "%s: Sanity check failed! SELECT command from client sd [%d] has no CRLF after it.", fn, Client->conn->sd );
151 return( -1 );
152 }
153 *CP = '\0';
154
155 Tag = Buf;
156 CP = memchr( ( const void * )Buf, ' ', SelectCmdLength );
157
158 if ( ! CP )
159 {
160 IMAPCount->SelectCacheMisses++;
161
162 syslog( LOG_ERR, "%s: Sanity check failed! No tokens found in SELECT command '%s' sent from client sd [%d].", fn, Buf, Client->conn->sd );
163 return( 1 );
164 }
165
166 *CP = '\0';
167 CP++;
168
169 Mailbox = memchr( ( const void * )CP, ' ', SelectCmdLength -
170 ( strlen( Tag ) + 1 ) );
171
172 if ( ! Mailbox )
173 {
174 IMAPCount->SelectCacheMisses++;
175
176 syslog( LOG_WARNING, "%s: Protocol error. Client sd [%d] sent SELECT command with no mailbox name: '%s'", fn, Client->conn->sd, SelectCmd );
177 snprintf( Buf, sizeof Buf - 1, "%s BAD missing required argument to SELECT command\r\n", Tag );
178 if ( IMAP_Write( Client->conn, Buf, strlen( Buf ) ) == -1 )
179 {
180 syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
181 return( -1 );
182 }
183 return( 0 );
184 }
185
186 Mailbox++;
187
188 /*
189 * We have a valid SELECT command. See if we have a cache entry that
190 * isn't expired.
191 */
192 if ( time( 0 ) > ( ISC->ISCTime + SELECT_CACHE_EXP ) )
193 {
194 /*
195 * The SELECT data that's cached has expired.
196 */
197 IMAPCount->SelectCacheMisses++;
198
199 rc = Populate_Select_Cache( Server, ISC, Mailbox, SelectCmd, SelectCmdLength );
200 if ( rc == -1 )
201 {
202 return( 1 );
203 }
204 if ( rc == -2 )
205 {
206 return( -2 );
207 }
208
209 rc = Send_Cached_Select_Response( Client, ISC, Tag );
210 if ( rc == -2 )
211 {
212 return( -1 );
213 }
214 if ( rc == -1 )
215 {
216 snprintf( Buf, sizeof Buf - 1, "%s BAD internal proxy server error\r\n", Tag );
217 if ( IMAP_Write( Client->conn, Buf, strlen( Buf ) ) == -1 )
218 {
219 syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
220 return( -1 );
221 }
222
223 // soft failure - we sent BAD response, so
224 // now let client decide what to do next
225 //
226 return( 0 );
227 }
228
229 return( 0 );
230 }
231
232
233 /*
234 * Our data isn't expired, but is it the correct mailbox?
235 */
236 if ( ! strcmp( Mailbox, ISC->MailboxName ) )
237 {
238 /*
239 * We have the correct mailbox selected and cached already
240 */
241 IMAPCount->SelectCacheHits++;
242
243 rc = Send_Cached_Select_Response( Client, ISC, Tag );
244 if ( rc == -2 )
245 {
246 return( -1 );
247 }
248 if ( rc == -1 )
249 {
250 snprintf( Buf, sizeof Buf - 1, "%s BAD internal proxy server error\r\n", Tag );
251 if ( IMAP_Write( Client->conn, Buf, strlen( Buf ) ) == -1 )
252 {
253 syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
254 return( -1 );
255 }
256
257 // soft failure - we sent BAD response, so
258 // now let client decide what to do next
259 //
260 return( 0 );
261 }
262
263 return( 0 );
264
265 }
266
267 IMAPCount->SelectCacheMisses++;
268
269 rc = Populate_Select_Cache( Server, ISC, Mailbox, SelectCmd, SelectCmdLength );
270 if ( rc == -1 )
271 {
272 return( 1 );
273 }
274 if ( rc == -2 )
275 {
276 return( -2 );
277 }
278
279 rc = Send_Cached_Select_Response( Client, ISC, Tag );
280 if ( rc == -2 )
281 {
282 return( -1 );
283 }
284 if ( rc == -1 )
285 {
286 snprintf( Buf, sizeof Buf - 1, "%s BAD internal proxy server error\r\n", Tag );
287 if ( IMAP_Write( Client->conn, Buf, strlen( Buf ) ) == -1 )
288 {
289 syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
290 return( -1 );
291 }
292
293 // soft failure - we sent BAD response, so
294 // now let client decide what to do next
295 //
296 return( 0 );
297 }
298
299 return( 0 );
300
301 }
302
303
304
305 /*++
306 * Function: Send_Cached_Select_Response
307 *
308 * Purpose: Send cached SELECT server response data back to a client.
309 *
310 * Parameters: ptr to ITD -- client transaction descriptor
311 * ptr to ISC -- IMAP select cache structure
312 * ptr to char -- client tag for response
313 *
314 * Returns: 0 on success
315 * -1 on soft failure
316 * -2 on hard failure
317 *
318 * Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
319 *
320 * Notes:
321 *--
322 */
Send_Cached_Select_Response(ITD_Struct * Client,ISC_Struct * ISC,char * Tag)323 static int Send_Cached_Select_Response( ITD_Struct *Client,
324 ISC_Struct *ISC,
325 char *Tag )
326 {
327 char *fn = "Send_Cached_Select_Response()";
328 char SendBuf[ BUFSIZE ];
329
330 if ( IMAP_Write( Client->conn, ISC->SelectString,
331 strlen( ISC->SelectString ) ) == -1 )
332 {
333 syslog( LOG_WARNING, "%s: Failed to send cached SELECT string to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
334 return( -2 );
335 }
336
337 snprintf( SendBuf, sizeof SendBuf - 1, "%s %s", Tag,
338 ISC->SelectStatus );
339
340 if ( IMAP_Write( Client->conn, SendBuf, strlen( SendBuf ) ) == -1 )
341 {
342 syslog( LOG_WARNING, "%s: Failed to send cached SELECT status to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
343 return( -2 );
344 }
345
346 return( 0 );
347
348 }
349
350
351
352
353 /*++
354 * Function: Populate_Select_Cache
355 *
356 * Purpose: Send a SELECT command to the server and cache the response.
357 *
358 * Parameters: ptr to ITD -- server transaction descriptor
359 * ptr to ISC -- the cache structure to populate
360 * ptr to char -- the mailbox name that's being selected
361 * ptr to char -- The select command string from the client.
362 * unsigned int -- the length of the select command
363 *
364 * Returns: 0 on success
365 * -1 on soft failure
366 * -2 on hard failure
367 *
368 * Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
369 *
370 * Notes:
371 *--
372 */
Populate_Select_Cache(ITD_Struct * Server,ISC_Struct * ISC,char * MailboxName,char * ClientCommand,unsigned int Length)373 static int Populate_Select_Cache( ITD_Struct *Server,
374 ISC_Struct *ISC,
375 char *MailboxName,
376 char *ClientCommand,
377 unsigned int Length )
378 {
379 char *fn = "Populate_Select_Cache()";
380 int rc;
381 int BytesLeftInBuffer = SELECT_BUF_SIZE;
382 char *BufPtr;
383 char *CP;
384 char *EOS;
385
386 rc = IMAP_Write( Server->conn, ClientCommand, Length );
387
388 if ( rc == -1 )
389 {
390 syslog( LOG_ERR, "%s: Unable to send SELECT command to IMAP server so can't populate cache.", fn );
391 return( -2 );
392 }
393
394 BufPtr = ISC->SelectString;
395
396 for( ;; )
397 {
398 if ( Server->LiteralBytesRemaining )
399 {
400 syslog( LOG_ERR, "%s: Server response to SELECT command contains unexpected literal data on sd [%d].",
401 fn, Server->conn->sd );
402 /*
403 * Must eat the literal.
404 */
405 while ( Server->LiteralBytesRemaining )
406 {
407 IMAP_Literal_Read( Server );
408 }
409
410 return( -1 );
411 }
412
413 rc = IMAP_Line_Read( Server );
414
415 if ( ( rc == -1 ) || ( rc == 0 ) )
416 {
417 syslog( LOG_WARNING, "%s: Unable to read SELECT response from IMAP server so can't populate cache.", fn );
418 return( -2 );
419 }
420
421 /*
422 * If it's not untagged data, we're done
423 */
424 if ( Server->ReadBuf[0] != '*' )
425 break;
426
427 if ( rc >= BytesLeftInBuffer )
428 {
429 syslog( LOG_WARNING, "%s: Size of SELECT response from server exceeds max cache size of %d bytes. Unable to cache this response.", fn, SELECT_BUF_SIZE );
430 return( -1 );
431 }
432
433 memcpy( (void *)BufPtr, (const void *)Server->ReadBuf, rc );
434 BytesLeftInBuffer -= rc;
435 BufPtr += rc;
436 }
437
438 /*
439 * NULL terminate the buffer that contains the select response. Note
440 * that we used the >= conditional above so we'd leave one byte of
441 * space for this NULL
442 */
443 *BufPtr = '\0';
444
445 /*
446 * The SELECT output string is filled in. Now fill in the status.
447 */
448 CP = memchr( (const void *)Server->ReadBuf, ' ', rc );
449 if ( ! CP )
450 {
451 syslog( LOG_ERR, "%s: Invalid response to SELECT command. Contains no tokens.", fn );
452 return( -1 );
453 }
454 CP++;
455
456 EOS = memchr( (const void *)Server->ReadBuf, '\r', rc );
457 if ( ! EOS )
458 {
459 syslog( LOG_ERR, "%s: Invalid response to SELECT command. Not CRLF terminated.", fn );
460 return( -2 );
461 }
462
463 *EOS = '\0';
464 snprintf( (char *)ISC->SelectStatus, SELECT_STATUS_BUF_SIZE - 1, "%s\r\n",
465 CP );
466 *EOS = '\r';
467
468 /*
469 * Update the cache time
470 */
471 ISC->ISCTime = time( 0 );
472
473 strncpy( (char *)ISC->MailboxName, (const char *)MailboxName, MAXMAILBOXNAME - 1 );
474 ISC->MailboxName[ MAXMAILBOXNAME - 1 ] = '\0';
475
476 return( 0 );
477
478 }
479
480
481
482
483 /*++
484 * Function: Is_Safe_Command
485 *
486 * Purpose: Determine whether a given command is "safe". A command that
487 * is not "safe" should cause the select cache for a given
488 * mailbox to be invalidated.
489 *
490 * Parameters: char ptr -- the command
491 *
492 * Returns: 1 if the command is safe
493 * 0 if the command is not safe
494 *
495 * Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
496 *
497 * Notes:
498 *--
499 */
Is_Safe_Command(char * Command)500 extern unsigned int Is_Safe_Command( char *Command )
501 {
502 unsigned int i;
503
504 /*
505 * The safety of some of these is very questionable, particularly
506 * APPEND. Since time also invalidates the cache, hopefully the
507 * contents will be accurate enough to not break stuff...
508 */
509 char *SafeCommands[] =
510 {
511 "CAPABILITY",
512 "NOOP",
513 "LOGOUT",
514 "STARTTLS",
515 "AUTHENTICATE",
516 "LOGIN",
517 "SELECT",
518 "EXAMINE",
519 "CREATE",
520 "DELETE",
521 "RENAME",
522 "SUBSCRIBE",
523 "UNSUBSCRIBE",
524 "LIST",
525 "LSUB",
526 "STATUS",
527 "APPEND",
528 "CHECK",
529 "SEARCH",
530 "FETCH",
531 "COPY",
532 "UID",
533 NULL
534 };
535
536 /*
537 * For a list this small a simple linear search should suffice.
538 */
539 for ( i = 0; SafeCommands[i] != 0; i++ )
540 {
541 if ( ! strncasecmp( SafeCommands[i], Command,
542 strlen( SafeCommands[i] ) ) )
543 {
544 return( 1 );
545 }
546 }
547
548 return( 0 );
549 }
550
551
552
553 /*++
554 * Function: Invalidate_Cache_Entry
555 *
556 * Purpose: Reset the cache time so the entry will not be valid
557 *
558 * Parameters: ptr to ISC -- IMAP select cache structure
559 *
560 * Returns: nothing
561 *
562 * Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
563 *
564 * Notes:
565 *--
566 */
Invalidate_Cache_Entry(ISC_Struct * ISC)567 extern void Invalidate_Cache_Entry( ISC_Struct *ISC )
568 {
569 ISC->ISCTime = 0;
570 }
571
572
573
574
575 /*
576 * _________
577 * / |
578 * / |
579 * / ______|
580 * / / ________
581 * | | | /
582 * | | |_____/
583 * | | ______
584 * | | | \
585 * | | |______\
586 * \ \_______
587 * \ |
588 * \ |
589 * \_________|
590 */
591
592