This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
jiangpenghui-qq-file-send/qq_file_send.c
姜鹏辉 45ce25f044 fix bugs
2020-12-17 17:01:40 +08:00

607 lines
20 KiB
C

#include <time.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <MESA/MESA_prof_load.h>
#include <MESA/MESA_handle_logger.h>
#include <MESA/MESA_htable.h>
#include <MESA/stream.h>
#include <nirvana_client.h>
#include "http.h"
#define LOG_PATH "./log/qq_file/runtime.log"
#define ONLINE_SEND 0
#define OFFLINE_SEND 1
#define NAME_SIZE 128
#define MAX_HTABLE_NUM 96
#define UUID_LENGTH 35
#define NIRVANA
int QQ_FILE_SEND_VERSION_1_20201124 = 0;
void qq_file_send_version_1_20201124()
{
//20201124
}
typedef struct
{
uint8_t send_type; // OFFLINE_SEND
char uuid[UUID_LENGTH+1];
uint16_t content_len; // content length in current packet
char filename[NAME_SIZE*2];
struct nirvana_streaming_ctx *ctx;
uint8_t next_flag; // 1: next packet contains separator
// 0: not contain
uint64_t *file_len; // file length
uchar curdir;
uint id; // same like file count , from 0 to start
}qq_pme_info,*qq_pme_info_p;
typedef struct
{
uint8_t feature_type;
char *separator_header;
uint header_len;
uint uuid_deviation;
char *uuid_suffix;
uint uuid_suffix_len;
uint content_deviation;
char *end_suffix;
uint end_suffix_len;
}struct_separator_feature;
typedef struct
{
uint id;
uint8_t send_type;
char filename[NAME_SIZE*2];
struct nirvana_streaming_ctx *ctx;
uint64_t *file_len; // file length
}file_info;
uint capture_file_cnt = 0;
struct_separator_feature online_feature,offline_feature;
MESA_htable_handle file_htable;
char save_directory[NAME_SIZE];
int locally_save; // save file to local machine
void *runtime_log;
struct nirvana_instance *instance_asyn;
/*
* @Description: Use this function instead of strncmp ,because strncmp may have problems matching features
* @param: the main string
* @param: the pattern string to be searched
* @param: the len of pattern sting
* @return: 0 : success
-1 : fail
*/
int bicmp(char *str,char *ptn,int len)
{
int i;
for(i = 0; i < len; i++)
{
if(str[i] != ptn[i])
return -1;
}
return 0;
}
/*
* @Description: Extract the information of private protocols in payload
* @param: payload
* @param: payload_len
* @param: features of separators
* @param: pme
* @return: =0 : success
<0 : fail
*/
int parse_separator(char **payload,int payload_len,struct_separator_feature separator_feature,qq_pme_info **pme)
{
int uuid_len = UUID_LENGTH;
//match by header
if( bicmp(*payload,separator_feature.separator_header, separator_feature.header_len) != 0)
{
return -1;
}
// match by the suffix which is after uuid
if( bicmp( (*payload)+separator_feature.uuid_deviation + uuid_len + 1, separator_feature.uuid_suffix,separator_feature.uuid_suffix_len) != 0)
{
return -2;
}
qq_pme_info_p qq_pme = *pme;
strncpy(qq_pme->uuid,(*payload) + separator_feature.uuid_deviation,uuid_len);
qq_pme->uuid[uuid_len] = '\0';
//printf("this uuid:%s\n",qq_pme->uuid);
(*payload)+=separator_feature.content_deviation;
qq_pme->content_len = payload_len-separator_feature.content_deviation;
qq_pme->send_type = separator_feature.feature_type;
return 0;
}
/*
* @Description: find end suffix in response payload
if found,means the end of the current file transfer
* @param: payload
* @param: payload_len
* @param: separator_feature
* @param: file_id
* @return: =0 : success
<0 : fail
*/
int parse_end_suffix(char *payload,int payload_len,struct_separator_feature separator_feature,uint file_id)
{
if( bicmp(separator_feature.end_suffix,payload,separator_feature.end_suffix_len) != 0)
{
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"match","file %u not finish",file_id);
return -1;
}
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"match","file %u finish",file_id);
return 0;
}
/*
* @Description: Converts a string in hex to decimal(int)
* @param: a hex char ,range : 0-9a-f
* @return: int , range : 0-15
*/
int hexch_to_int(char ch)
{
if ((ch >= 'A') && (ch <='F'))
{
return 10+ch-'A';
}
else if ((ch >= 'a') && (ch <='f'))
{
return 10+ch-'a';
}
else{
return ch-'0';
}
}
/*
* @Description: 'abcd00' -> {0xab,0xcd,0x00}
* @param: in_str
* @param: the len of out_str
* @param: out_str
* @return: None
*/
void str_to_ascii(char *in_str,int str_len,char* out_str)
{
int i;
for (i=0;i<str_len;i++)
{
out_str[i] = hexch_to_int(in_str[i*2])*16 + hexch_to_int(in_str[i*2+1]);
}
}
/*
* @Description: read the feature of the separator in the profile
* @param: [IN] profile name
* @param: [OUT] p_separator_feature
* @param: [IN] section name
* @param: [IN] feature type eg ONLINE_SEND/OFFLINE_SEND
* @return:
0 : success
>0: fail
*/
int read_profile_of_separator(const char *inf_file,struct_separator_feature *p_separator_feature,char *section,int feature_type)
{
char *separator_header_array,*uuid_suffix_array,*end_suffix_array;
int read_res = 0;
p_separator_feature->feature_type = feature_type;
//header_len
read_res += MESA_load_profile_uint_nodef(inf_file,section,"header_len",&(p_separator_feature->header_len));
//header
p_separator_feature->separator_header = (char *)malloc(p_separator_feature->header_len+1);
separator_header_array = (char *)malloc((p_separator_feature->header_len)<<1|1);
if(MESA_load_profile_string_nodef(inf_file,section,"separator_header",separator_header_array,p_separator_feature->header_len<<1|1)<0)
{
read_res += -1;
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"init","[%s] separator_header error",section);
}
str_to_ascii(separator_header_array,p_separator_feature->header_len,p_separator_feature->separator_header);
free(separator_header_array);
//uuid_deviation
read_res += MESA_load_profile_uint_nodef(inf_file,section,"uuid_deviation",&(p_separator_feature->uuid_deviation));
//uuid_suffix_len
read_res += MESA_load_profile_uint_nodef(inf_file,section,"uuid_suffix_len",&(p_separator_feature->uuid_suffix_len));
//uuid_suffix
p_separator_feature->uuid_suffix = (char *)malloc(p_separator_feature->uuid_suffix_len+1);
uuid_suffix_array = (char *)malloc(p_separator_feature->uuid_suffix_len<<2|1);
if(MESA_load_profile_string_nodef(inf_file,section,"uuid_suffix",uuid_suffix_array,p_separator_feature->uuid_suffix_len<<1|1)<0)
{
read_res += -1;
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"init","[%s] uuid_suffix error",section);
}
str_to_ascii(uuid_suffix_array,p_separator_feature->uuid_suffix_len,p_separator_feature->uuid_suffix);
free(uuid_suffix_array);
//content_deviation
read_res += MESA_load_profile_uint_nodef(inf_file,section,"content_deviation",&(p_separator_feature->content_deviation));
//end_suffix_len
read_res += MESA_load_profile_uint_nodef(inf_file,section,"end_suffix_len",&(p_separator_feature->end_suffix_len));
//end_suffix
p_separator_feature->end_suffix = (char *)malloc(p_separator_feature->end_suffix_len+1);
end_suffix_array = (char *)malloc(p_separator_feature->end_suffix_len<<1|1);
if(MESA_load_profile_string_nodef(inf_file,section,"end_suffix",end_suffix_array,p_separator_feature->end_suffix_len<<1|1)<0)
{
read_res += -1;
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"init","[%s] end_suffix error",section);
}
str_to_ascii(end_suffix_array,p_separator_feature->end_suffix_len,p_separator_feature->end_suffix);
free(end_suffix_array);
return read_res;
}
/*
* @Description: Used to delete value from hash table
* @param: file_info *
* @return: Null
*/
void free_fileinfo(void *thisfile)
{
//thisfile = thisfile;
file_info *p = (file_info *)thisfile;
#ifdef NIRVANA
nirvana_streaming_update_end(p->ctx);
#endif
if (p->send_type == OFFLINE_SEND)
MESA_handle_runtime_log(runtime_log,RLOG_LV_INFO,"offline","save file %d , size: %lld bytes",p->id, *(p->file_len));
else
MESA_handle_runtime_log(runtime_log,RLOG_LV_INFO,"online","save file %d , size: %lld bytes",p->id, *(p->file_len));
if(p->file_len)
free(p->file_len);
free(p);
}
/*
* @Description: different files have different uuids.
Fragments with the same UUID belong to the same file
get the pme according to the current uuid
* @param: pme
* @param: content len
* @param: send_type
* @return:
0: not exist
1: exist
*/
int match_uuid(qq_pme_info **pme)
{
qq_pme_info_p qq_pme = *pme;
file_info* thisfile = (file_info *) MESA_htable_search(file_htable,(u_char *)qq_pme->uuid,UUID_LENGTH);
if (thisfile == NULL)
{
// new file create
file_info *newfile;
newfile = (file_info *)malloc(sizeof(file_info));
qq_pme->id = capture_file_cnt;
capture_file_cnt++;
newfile->id = qq_pme->id;
sprintf(qq_pme->filename,"%s/%d-%s",save_directory,qq_pme->id,qq_pme->uuid);
strncpy(newfile->filename,qq_pme->filename,NAME_SIZE);
newfile->filename[strlen(qq_pme->filename)] = '\0';
uint64_t *file_len = (uint64_t *)malloc(sizeof(uint64_t));
*file_len = 0;
qq_pme->file_len = file_len;
newfile->file_len = file_len;
newfile->send_type = qq_pme->send_type;
#ifdef NIRVANA
// nirvana section
struct nirvana_streaming_ctx *ctx;
union nirvana_progid progid;
struct streaming_meta streammeta;
struct nvn_opt_unit opt[1];
int opt_num = 0;
memset(&streammeta,0,sizeof(streammeta));
streammeta.msg_type = NVN_MSG_TYPE_DOC_IM;
if(qq_pme->send_type == ONLINE_SEND)
streammeta.msg_subtype = NVN_MSG_SUBTYPE_IM_QQ_ONLINE;
else
streammeta.msg_subtype = NVN_MSG_SUBTYPE_IM_QQ_OFFLINE;
streammeta.protocol = NVN_PROTOCOL_QQ;
streammeta.cont_code = 0;
streammeta.live_streaming = 0;
streammeta.cap_IP = 0;
streammeta.dir = DIR_C2S;
streammeta.request_single_flow = 0;
calculate_md5_for_progid(newfile->filename, strlen(newfile->filename), &progid);
streammeta.balance_seed = caculate_seed_using_sdbm_hash((char*)&progid, sizeof(progid));
ctx = nirvana_streaming_update_start(instance_asyn, &progid, &streammeta);
if(ctx == NULL)
{
return PROT_STATE_DROPME;
}
nirvana_streaming_update_meta(ctx, opt, opt_num);
qq_pme->ctx = ctx;
newfile->ctx = ctx;
#endif
MESA_handle_runtime_log(runtime_log,RLOG_LV_INFO,"match","found %d file\tuuid:%s ",qq_pme->id,qq_pme->uuid);
MESA_htable_add(file_htable,(u_char *) qq_pme->uuid, UUID_LENGTH, (void *)newfile);
return 0;
}
else
{
// there is a file point in htable
// so change the pme
qq_pme->id = thisfile->id;
qq_pme->file_len = thisfile->file_len;
qq_pme->send_type = thisfile->send_type;
strncpy(qq_pme->filename,thisfile->filename,strlen(thisfile->filename));
#ifdef NIRVANA
qq_pme->ctx = thisfile->ctx;
#endif
return 1;
}
}
/*
* @Description: free memory of pme
*/
void free_pme(qq_pme_info **pme)
{
qq_pme_info_p qq_pme = *pme;
//free(qq_pme->ctx);
free(qq_pme);
}
uchar qq_file_send_entry(stSessionInfo* session_info,void **pme,int thread_seq,struct streaminfo *a_tcp,void *a_packet)
{
int res = 0;
uchar rec = PROT_STATE_GIVEME;
//int payload_len = tcp_detail->datalen;
//char *payload =(char *)tcp_detail->pdata;
qq_pme_info_p qq_pme = (qq_pme_info_p) *pme;
http_infor* a_http = (http_infor *)(session_info->app_info);
switch(session_info->prot_flag)
{
case HTTP_REQ_LINE:
if(a_http->method!=HTTP_METHOD_POST)
{
rec = PROT_STATE_DROPME;
}
if (*pme == NULL)
{
*pme =(qq_pme_info_p)malloc(sizeof(qq_pme_info));
qq_pme = *pme;
memset(qq_pme->filename,0,NAME_SIZE);
}
qq_pme->curdir = a_http->curdir;
break;
case HTTP_RES_LINE:
if (qq_pme == NULL)
{
rec = PROT_STATE_DROPME;
}
break;
case HTTP_MESSAGE_URL:
{
char bmd5[33];
int url_decoded_len;
url_decoded_len = session_info->buflen;
char *url_decoded = (char*)calloc(url_decoded_len+1,sizeof(char));
memcpy(url_decoded, session_info->buf, session_info->buflen);
res = sscanf(url_decoded,"%*d.%*d.%*d.%*d:%*d/ftn_handler?bmd5=%32s",bmd5);
if(res>0)
{
qq_pme->next_flag = 0;
// separator_flag is 1 means that the next TCP packet contains a separator
}
else
{
rec = PROT_STATE_DROPME;
}
free(url_decoded);
break;
}
case HTTP_CONTENT:
{
int content_len = session_info->buflen;
char *payload = (char *)malloc(content_len);
memcpy(payload,session_info->buf,content_len);
char *content = payload;
if (content_len==0)
{
free(payload);
break;
}
if (qq_pme->curdir != a_http->curdir)
{
// looking for end suffix
if(qq_pme->send_type == ONLINE_SEND)
{
res = parse_end_suffix(content,content_len,online_feature,qq_pme->id);
}
else
{
res = parse_end_suffix(content,content_len,offline_feature,qq_pme->id);
}
if(res == 0)
{
MESA_htable_del(file_htable,(u_char *)qq_pme->uuid,UUID_LENGTH,free_fileinfo);
}
}
else
{
if(qq_pme->next_flag == 0)
{
res = parse_separator(&content,content_len,offline_feature,&qq_pme);
if(res < 0)
{
// If offline matching fails, the online rule will be matched again
res = parse_separator(&content,content_len,online_feature,&qq_pme);
if(res < 0)
{
// If both matches fail, the rule sis wrong or the traffic is wrong
if (res == -1)
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"match","feature fail,check rule");
else
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"match","feature fail,unkown type");
// if the current http stream is transferring a file with a half,and the rule matches fail
// there will be a streamctx still open and no way to close
// but this situation seems won't happen
rec = PROT_STATE_DROPME;
free(payload);
break;
}
else
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"match","online feature success,uuid:%s",qq_pme->uuid);
}
else
{
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"match","offline feature success,uuid:%s",qq_pme->uuid);
}
qq_pme->next_flag = 1;
content_len = qq_pme->content_len;
match_uuid(&qq_pme);
}
*(qq_pme->file_len) += content_len;
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"match","file %d saved %d Bytes, content_len:%d",qq_pme->id,*(qq_pme->file_len),content_len);
if(locally_save == 1)
{
//save file to disk
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"match","file %d saved as %s",qq_pme->id,qq_pme->filename);
FILE *fp = fopen(qq_pme->filename,"ab");
fwrite(content,content_len,1,fp);
fclose(fp);
}
#ifdef NIRVANA
nirvana_streaming_update_data(qq_pme->ctx, content, content_len);
#endif
}
free(payload);
break;
}
}
if(session_info->session_state&SESSION_STATE_CLOSE || rec == PROT_STATE_DROPME)
{
if(pme != NULL)
{
free_pme((qq_pme_info **)pme);
}
}
return rec;
}
int suggestion_inform_callback(struct suggest_receive_info *info, struct nirvana_message_survey *detail, const char *snap_URL, struct nvn_opt_unit *opts, void *privdata)
{
char fromip_str[64];
inet_ntop(AF_INET, &info->from_ip, fromip_str, 64);
printf("Receive suggest from: %s, progid=0x%lu_%lu\n", fromip_str, detail->progid.progid_long.progidH, detail->progid.progid_long.progidL);
return 0;
}
void nirvana_client_init()
{
struct nirvana_parameter *parameter;
parameter = nirvana_client_parameter_new("./plug/business/qq_file_send/qq_file_send.conf", "NIRVANA", runtime_log);
//assert(parameter != NULL);
instance_asyn = nirvana_evbase_instance_new(parameter, runtime_log, suggestion_inform_callback, NULL);
//assert(instance_asyn!=NULL);
}
void QQ_FILE_SEND_DESTROY()
{
MESA_handle_runtime_log(runtime_log,RLOG_LV_INFO,"destroy","------------exit------------");
}
int qq_file_send_init()
{
printf("qq_file_send init\n");
int read_res = 0;
runtime_log = MESA_create_runtime_log_handle(LOG_PATH, 20);
char *inf_file = "./plug/business/qq_file_send/qq_file_send.conf";
read_res += read_profile_of_separator(inf_file,&online_feature,"ONLINE_FEATURE",ONLINE_SEND);
read_res += read_profile_of_separator(inf_file,&offline_feature,"OFFLINE_FEATURE",OFFLINE_SEND);
//read_res += MESA_load_profile_string_nodef(inf_file,"CAPTURE","post_header",post_header,NAME_SIZE);
MESA_load_profile_int_def(inf_file,"CAPTURE","locally_save",&locally_save,0);
if(locally_save == 1)
{
if(MESA_load_profile_string_def(inf_file,"CAPTURE","save_directory",save_directory,NAME_SIZE,"./plug/business/qq_file_send/")<0)
{
read_res += -1;
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"init","load save directory failed");
}
}
else
{
strcpy(save_directory,"./plug/business/qq_file_send/");
}
if(access(save_directory,W_OK) != 0 && locally_save == 1)
{
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"init","save directory %s not found or cannot write\n",save_directory);
return -1;
}
else
{
MESA_handle_runtime_log(runtime_log,RLOG_LV_DEBUG,"init","save directory is %s",save_directory);
}
//free(inf_file);
if(read_res == 0)
MESA_handle_runtime_log(runtime_log,RLOG_LV_INFO,"init","------------load feature success------------");
else
{
MESA_handle_runtime_log(runtime_log,RLOG_LV_FATAL,"init","------------load feature failed:%d------------",read_res);
return -1;
}
#ifdef NIRVANA
nirvana_client_init();
#endif
MESA_htable_create_args_t *htable_args;
htable_args = (MESA_htable_create_args_t *)malloc(sizeof(MESA_htable_create_args_t));
htable_args->thread_safe = 1;
htable_args->recursive = 0;
htable_args->hash_slot_size = 1048576;
htable_args->max_elem_num = MAX_HTABLE_NUM;
htable_args->key_comp = NULL;
htable_args->key2index = NULL;
htable_args->data_free = free_fileinfo;
htable_args->data_expire_with_condition = NULL;
htable_args->eliminate_type = 0;
htable_args->expire_time = 60;
file_htable = MESA_htable_create(htable_args,1);
MESA_htable_print_crtl(file_htable,0);
return 0;
}