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