diff --git a/src/config.c b/src/config.c index dec47b2..04bc219 100644 --- a/src/config.c +++ b/src/config.c @@ -202,6 +202,16 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf ) conf->sync_state = expand_strdup( cfile->val ); else if (!strcasecmp( "CopyArrivalDate", cfile->cmd )) conf->use_internal_date = parse_bool( cfile ); + else if (!strcasecmp( "CutLongLines", cfile->cmd )) + conf->cut_lines = parse_bool( cfile ); + else if (!strcasecmp( "IgnoreMaxPulledUid", cfile->cmd )) + conf->ignore_max_pulled_uid = parse_bool( cfile ); + else if (!strcasecmp( "SkipBinaryContent", cfile->cmd )) + conf->skip_binary_content = parse_bool( cfile ); + else if (!strcasecmp( "DeleteNonEmpty", cfile->cmd )) + conf->delete_nonempty = parse_bool( cfile ); + else if (!strcasecmp( "MaxLineLength", cfile->cmd )) + conf->max_line_len = parse_int( cfile ); else if (!strcasecmp( "MaxMessages", cfile->cmd )) conf->max_messages = parse_int( cfile ); else if (!strcasecmp( "ExpireUnread", cfile->cmd )) diff --git a/src/drv_imap.c b/src/drv_imap.c index c5a7aed..6d0a7e1 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -78,6 +78,7 @@ typedef union imap_store_conf { char delimiter; char use_namespace; char use_lsub; + char ignore_keyword_warnings; }; } imap_store_conf_t; @@ -1627,7 +1628,8 @@ imap_socket_read( void *aux ) error( "IMAP error: bogus greeting response %s\n", arg ); break; } else if (!strcmp( "NO", arg )) { - warn( "Warning from IMAP server: %s\n", cmd ); + if (!ctx->conf->ignore_keyword_warnings || strcmp( cmd, "Keywords are not supported" )) + warn( "Warning from IMAP server: %s\n", cmd ); } else if (!strcmp( "BAD", arg )) { error( "Error from IMAP server: %s\n", cmd ); } else if (!strcmp( "CAPABILITY", arg )) { @@ -1725,8 +1727,16 @@ imap_socket_read( void *aux ) resp = RESP_NO; if (cmdp->param.failok) goto doresp; - } else /*if (!strcmp( "BAD", arg ))*/ - resp = RESP_CANCEL; + } else /*if (!strcmp( "BAD", arg ))*/ { + //resp = RESP_CANCEL; + // Ignore BAD Error 10 (or 11) when SkipBinaryContent is not used. + // this doesn't seem like a terribly good idea to me - this server response + // indicates that the client (allegedly) did something wrong. that may mean + // that the subsequent command stream will be interpreted as garbage, which + // may have unpredictable effects. it just isn't safe to continue at this + // point. + resp = RESP_NO; + } error( "IMAP command '%s' returned an error: %s %s\n", starts_with( cmdp->cmd, -1, "LOGIN", 5 ) ? "LOGIN " : @@ -3671,6 +3681,8 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) store->use_namespace = parse_bool( cfg ); else if (!strcasecmp( "SubscribedOnly", cfg->cmd )) store->use_lsub = parse_bool( cfg ); + else if (!strcasecmp( "IgnoreKeywordWarnings", cfg->cmd )) + store->ignore_keyword_warnings = parse_bool( cfg ); else if (!strcasecmp( "Path", cfg->cmd )) store->path = nfstrdup( cfg->val ); else if (!strcasecmp( "PathDelimiter", cfg->cmd )) { diff --git a/src/sync.c b/src/sync.c index eb59e5d..3a8f511 100644 --- a/src/sync.c +++ b/src/sync.c @@ -376,13 +376,15 @@ copy_msg( copy_vars_t *vars ) static void msg_stored( int sts, uint uid, void *aux ); static void -copy_msg_bytes( char **out_ptr, const char *in_buf, uint *in_idx, uint in_len, int in_cr, int out_cr ) +copy_msg_bytes( char **out_ptr, const char *in_buf, uint *in_idx, uint in_len, int in_cr, int out_cr, uint max_line_len ) { char *out = *out_ptr; uint idx = *in_idx; if (out_cr != in_cr) { + /* message needs to be converted */ char c; if (out_cr) { + /* adding CR */ for (; idx < in_len; idx++) { if ((c = in_buf[idx]) != '\r') { if (c == '\n') @@ -391,15 +393,52 @@ copy_msg_bytes( char **out_ptr, const char *in_buf, uint *in_idx, uint in_len, i } } } else { + /* removing CR */ for (; idx < in_len; idx++) { if ((c = in_buf[idx]) != '\r') *out++ = c; } } } else { - memcpy( out, in_buf + idx, in_len - idx ); - out += in_len - idx; - idx = in_len; + /* no CRLF change */ + if (max_line_len > 0) { + /* there are too long lines in the message */ + const char *curLine = in_buf + *in_idx; + int lines = 0; + while (curLine) { + char *nextLine = strchr( curLine, '\n' ); + uint curLineLen = nextLine ? (unsigned int)(nextLine-curLine) + 1 : strlen( curLine ); + if (curLineLen > max_line_len) { + /* this line need to be cut into smaller lines */ + uint line_idx = 0; + while (line_idx < curLineLen) { + memcpy( out, in_buf + idx + line_idx, ((curLineLen - line_idx) < max_line_len) ? (curLineLen - line_idx) : max_line_len ); + out += (((curLineLen - line_idx) < max_line_len) ? (curLineLen - line_idx) : max_line_len); + line_idx += ((curLineLen - line_idx) < max_line_len) ? (curLineLen - line_idx) : max_line_len; + if( line_idx < curLineLen) { + /* add (CR)LF except for the last line */ + if (out_cr) + *out++ = '\r'; + *out++ = '\n'; + } + } + idx += curLineLen; + } else { + /* simple copy */ + memcpy( out , in_buf + idx, curLineLen ); + out += curLineLen; + idx += curLineLen; + } + curLine = nextLine ? (nextLine+1) : NULL; + lines++; + } + //debug("End index %d (message size %d), message size should be %d\n", idx, in_len, *in_idx + out - *out_ptr); + } else { + /* simple copy */ + memcpy( out, in_buf + idx, in_len - idx ); + out += in_len - idx; + idx = in_len; + } } *out_ptr = out; *in_idx = idx; @@ -411,10 +450,47 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t ) char *in_buf = vars->data.data; uint in_len = vars->data.len; uint idx = 0, sbreak = 0, ebreak = 0, break2 = UINT_MAX; - uint lines = 0, hdr_crs = 0, bdy_crs = 0, app_cr = 0, extra = 0; + uint lines = 0, hdr_crs = 0, bdy_crs = 0, app_cr = 0, extra = 0, extra_bytes = 0; uint add_subj = 0; if (vars->srec) { + /* check if the message has too long lines if enabled */ + if (global_conf.max_line_len) { + char *curLine = in_buf; + while (curLine) { + char *nextLine = strchr( curLine, '\n' ); + uint curLineLen = nextLine ? (unsigned int)(nextLine-curLine) + 1 : strlen( curLine ); + if (curLineLen > global_conf.max_line_len) { + if (global_conf.cut_lines) { + /* compute the addded lines as we are going to cut them */ + if (out_cr) + extra_bytes += curLineLen / global_conf.max_line_len; // CR + extra_bytes += curLineLen / global_conf.max_line_len; // LF + } else { + /* stop here with too long line error */ + warn( "Warning: message %u from %s has too long line(s).\n", + vars->msg->uid, str_fn[1-t] ); + free( in_buf ); + return 0; + } + } + curLine = nextLine ? (nextLine+1) : NULL; + } + } + if (global_conf.skip_binary_content) { + while (idx < in_len) { + unsigned char c = in_buf[idx++]; + if (c < 0x20 && c != '\r' && c != '\n' && c != '\t') { + /* binary content, skip */ + debug( "Incorrect byte %u at offset %u/%u\n", c, idx, in_len ); + warn( "Warning: message %u from %s has raw binary content.\n", + vars->msg->uid, str_fn[1-t] ); + free( in_buf ); + return 0; + } + } + idx = 0; + } nloop: ; uint start = idx; uint line_crs = 0; @@ -494,7 +570,7 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t ) extra += add_subj ? strlen(dummy_subj) + app_cr + 1 : strlen(dummy_pfx); } - vars->data.len = in_len + extra; + vars->data.len = in_len + extra + extra_bytes; if (vars->data.len > INT_MAX) { warn( "Warning: message %u from %s is too big after conversion; skipping.\n", vars->msg->uid, str_fn[1-t] ); @@ -505,11 +581,12 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t ) idx = 0; if (vars->srec) { if (break2 < sbreak) { - copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr ); + copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr, 0 ); memcpy( out_buf, dummy_pfx, strlen(dummy_pfx) ); out_buf += strlen(dummy_pfx); } - copy_msg_bytes( &out_buf, in_buf, &idx, sbreak, in_cr, out_cr ); + //debug ("Calling copy_msg_bytes for the header (0 to %d) with %d extra bytes\n", sbreak, extra); + copy_msg_bytes( &out_buf, in_buf, &idx, sbreak, in_cr, out_cr, 0 ); memcpy( out_buf, "X-TUID: ", 8 ); out_buf += 8; @@ -521,7 +598,7 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t ) idx = ebreak; if (break2 != UINT_MAX && break2 >= sbreak) { - copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr ); + copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr, 0 ); if (!add_subj) { memcpy( out_buf, dummy_pfx, strlen(dummy_pfx) ); out_buf += strlen(dummy_pfx); @@ -534,7 +611,10 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t ) } } } - copy_msg_bytes( &out_buf, in_buf, &idx, in_len, in_cr, out_cr ); + //debug ("Calling copy_msg_bytes for the body (at %d) with %d extra byte(s), limit is %d \n", ebreak, extra_bytes, extra_bytes > 0 ? global_conf.max_line_len : 0); + copy_msg_bytes( &out_buf, in_buf, &idx, in_len, in_cr, out_cr, extra_bytes > 0 ? global_conf.max_line_len : 0 ); + //debug("Message after %s\n", vars->data.data); + //debug("Good message size should be %d + %d\n",vars->data.len-extra, extra); if (vars->minimal) memcpy( out_buf, dummy_msg_buf, dummy_msg_len ); @@ -565,7 +645,7 @@ msg_fetched( int sts, void *aux ) tcr = (svars->drv[t]->get_caps( svars->ctx[t] ) / DRV_CRLF) & 1; if (vars->srec || scr != tcr) { if (!copy_msg_convert( scr, tcr, vars, t )) { - vars->cb( SYNC_NOGOOD, 0, vars ); + vars->cb( SYNC_MALFORMED, 0, vars ); return; } } @@ -1279,7 +1359,7 @@ box_confirmed2( sync_vars_t *svars, int t ) svars->chan->name, str_fn[t], svars->orig_name[t] ); goto bail; } - if (svars->drv[1-t]->confirm_box_empty( svars->ctx[1-t] ) != DRV_OK) { + if (!global_conf.delete_nonempty && svars->drv[1-t]->confirm_box_empty( svars->ctx[1-t] ) != DRV_OK) { warn( "Warning: channel %s: %s box %s cannot be opened and %s box %s is not empty.\n", svars->chan->name, str_fn[t], svars->orig_name[t], str_fn[1-t], svars->orig_name[1-t] ); goto done; @@ -1824,7 +1904,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux } else { if (!(svars->chan->ops[t] & OP_NEW)) continue; - if (tmsg->uid <= svars->maxuid[1-t]) { + if (!global_conf.ignore_max_pulled_uid && tmsg->uid <= svars->maxuid[1-t]) { // The message should be already paired. It's not, so it was: // - previously paired, but the entry was expired and pruned => ignore // - attempted, but failed => ignore (the wisdom of this is debatable) @@ -2073,6 +2153,9 @@ msg_copied( int sts, uint uid, copy_vars_t *vars ) ASSIGN_UID( srec, t, uid, "%sed message", str_hl[t] ); } break; + case SYNC_MALFORMED: + assign_uid( svars, vars->srec, t, 0 ); + break; case SYNC_NOGOOD: srec->status = S_DEAD; JLOG( "- %u %u", (srec->uid[F], srec->uid[N]), "%s failed", str_hl[t] ); diff --git a/src/sync.h b/src/sync.h index ad0478b..0f5d4b6 100644 --- a/src/sync.h +++ b/src/sync.h @@ -56,6 +56,11 @@ typedef struct channel_conf { int max_messages; // For near side only. signed char expire_unread; char use_internal_date; + uint max_line_len; + char cut_lines; + char ignore_max_pulled_uid; /* for master only */ + char skip_binary_content; /* for master only */ + char delete_nonempty; } channel_conf_t; typedef struct group_conf { @@ -75,6 +80,7 @@ extern const char *str_fn[2], *str_hl[2]; #define SYNC_BAD(fn) (4<<(fn)) #define SYNC_NOGOOD 16 /* internal */ #define SYNC_CANCELED 32 /* internal */ +#define SYNC_MALFORMED 64 /* internal */ #define BOX_POSSIBLE -1 #define BOX_ABSENT 0