static int parse_ops(char* act_s, struct dbops_action** action, int has_name) { int res = 0, i; char *c, *s, *part; static int query_no = 0; s = act_s; *action = pkg_malloc(sizeof(**action)); if (!*action) return E_OUT_OF_MEM; memset(*action, 0, sizeof(**action)); (*action)->query_no = query_no++; eat_spaces(s); c = s; eat_alphanum(c); if (has_name) { char *c2; c2 = c; eat_spaces(c2); if (c != s && *c2 == '=') { *c = '\0'; if (find_action_by_name(s, -1) != NULL) { ERR(MODULE_NAME": parse_ops: duplicate query name: %s\n", s); return E_CFG; } (*action)->query_name = s; s = c2+1; eat_spaces(s); c = s; eat_alphanum(c); } else { ERR(MODULE_NAME": parse_ops: query_no: %d, valid query name not found in '%s'\n%s\n%s\n", (*action)->query_no, s, c, c2); return E_CFG; } } if (c[0] == ':' && c[1] == '/' && c[2] == '/') { /* database part is optional */ for (c=s; *c!=':'; c++) { *c = tolower(*c); /* _type_://user:host/database_name/ */ } (*action)->db_url = s; s = c+1; while (*s == '/') s++; res = get_next_part(&s, &part, PART_DELIM, 1); /* type://_user:host_/database_name/ */ if (res < 0) return res; res = get_next_part(&s, &part, PART_DELIM, 0); /* type://user:host/_database_name_/ */ if (res < 0) return res; } res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; for (c = part; *c && *c != PART_DELIM; c++) { if (*c == ' ') { (*action)->is_raw_query = 1; *c = '\0'; break; } } if (strcasecmp(part, "select") == 0) (*action)->operation = OPEN_QUERY_OPS; else if (strcasecmp(part, "insert") == 0) (*action)->operation = INSERT_OPS; else if (strcasecmp(part, "update") == 0) (*action)->operation = UPDATE_OPS; else if (strcasecmp(part, "replace") == 0) (*action)->operation = REPLACE_OPS; else if (strcasecmp(part, "delete") == 0) (*action)->operation = DELETE_OPS; else { if ((*action)->is_raw_query) *c = ' '; ERR(MODULE_NAME": parse_ops: query: %s(%d), unknown type of query '%s'\n", (*action)->query_name, (*action)->query_no, part); return E_CFG; } if ((*action)->is_raw_query) { *c = ' '; (*action)->raw.s = part; (*action)->table.s = part; } res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; if (!(*action)->is_raw_query) { if (!*part) { ERR(MODULE_NAME": parse_ops: query: %s(%d), table not specified near '%s' in '%s'\n", (*action)->query_name, (*action)->query_no, s, act_s); return E_CFG; } trim_apostr(&part); (*action)->table.s = part; res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; switch ((*action)->operation) { case OPEN_QUERY_OPS: case UPDATE_OPS: case REPLACE_OPS: case INSERT_OPS: res = split_fields(part, &(*action)->field_count, &(*action)->fields); if (res < 0) return res; if ((*action)->field_count == 0) { ERR(MODULE_NAME": parse_ops: query: %s(%d), no field specified near '%s' ?n '%s'\n", (*action)->query_name, (*action)->query_no, part, act_s); return E_CFG; } break; case DELETE_OPS: res = split_fields(part, &(*action)->where_count, &(*action)->wheres); if (res < 0) return res; res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; res = split_fields(part, &(*action)->op_count, &(*action)->ops); if (res < 0) return res; break; default:; } res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; switch ((*action)->operation) { case OPEN_QUERY_OPS: case UPDATE_OPS: res = split_fields(part, &(*action)->where_count, &(*action)->wheres); if (res < 0) return res; res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; res = split_fields(part, &(*action)->op_count, &(*action)->ops); if (res < 0) return res; res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; switch ((*action)->operation) { case OPEN_QUERY_OPS: if (*part) { (*action)->order.s = part; } res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; break; default:; } break; default: ; } } /* values */ res = split_fields(part, &(*action)->value_count, &(*action)->values); if (res < 0) return res; if ((*action)->value_count) { (*action)->value_types = (int*)pkg_malloc(sizeof(int) * (*action)->value_count); if ((*action)->value_types == NULL) { ERR(MODULE_NAME": No memory left\n"); return -1; } for (i=0; i<(*action)->value_count; i++) { (*action)->value_types[i] = DB_CSTR; // DB_NONE; /* let decide db driver itself, FIXME: until jjanak changes then default type is string */ res = get_type(&(*action)->values[i].s, &(*action)->value_types[i]); if (res < 0) return res; } } /* extra options */ res = get_next_part(&s, &part, PART_DELIM, 0); if (res < 0) return res; (*action)->extra_ops_count = 0; c = part; while (*c) { char *fld; res = get_next_part(&c, &fld, FLD_DELIM, 1); if (res < 0) return res; (*action)->extra_ops_count++; } if ((*action)->extra_ops_count > 0) { (*action)->extra_ops = pkg_malloc( (*action)->extra_ops_count*sizeof(*(*action)->extra_ops)); if (!(*action)->extra_ops) { ERR(MODULE_NAME": parse_ops: not enough pkg memory\n"); return E_OUT_OF_MEM; } memset((*action)->extra_ops, 0, (*action)->extra_ops_count*sizeof(*(*action)->extra_ops)); i = 0; c = part; while (*c) { char *fld; res = get_next_part(&c, &fld, FLD_DELIM, 0); if (res < 0) return res; /* name=[i|s]:value */ (*action)->extra_ops[i].name = fld; eat_alphanum(fld); if (*fld != '=') { ERR(MODULE_NAME": parse_ops: query: %s(%d), bad extra parameter format in '%s'\n", (*action)->query_name, (*action)->query_no, (*action)->extra_ops[i].name); return E_CFG; } *fld = '\0'; fld++; while (*fld==' ' || *fld=='\t') fld++; (*action)->extra_ops[i].type = DB_NONE; res = get_type(&fld, &(*action)->extra_ops[i].type); if (res < 0) return res; trim_apostr(&fld); (*action)->extra_ops[i].value = fld; DEBUG(MODULE_NAME": extra_ops #%d, name='%s', type=%d, val='%s'\n", i, (*action)->extra_ops[i].name, (*action)->extra_ops[i].type, (*action)->extra_ops[i].value); i++; } } if (*s) { ERR(MODULE_NAME": parse_ops: query: %s(%d), too many parameters/parts, remaining '%s' in '%s'\n", (*action)->query_name, (*action)->query_no, s, act_s); return E_CFG; } if ((*action)->is_raw_query) { DEBUG(MODULE_NAME": query: %s(%d) oper:%d database:'%s' query:'%s' value#:%d extra_ops#:%d\n", (*action)->query_name, (*action)->query_no, (*action)->operation, (*action)->db_url, (*action)->raw.s, (*action)->value_count, (*action)->extra_ops_count); } else { /* check num of fields */ if ((((*action)->operation==OPEN_QUERY_OPS)?0:(*action)->field_count)+(*action)->where_count != (*action)->value_count) { ERR(MODULE_NAME": parse_ops: query: %s(%d), number of values does not correspond to number of fields (%d+%d!=%d) in '%s'\n", (*action)->query_name, (*action)->query_no, ((*action)->operation==OPEN_QUERY_OPS)?0:(*action)->field_count, (*action)->where_count, (*action)->value_count, act_s); return E_CFG; } DEBUG(MODULE_NAME": query_no:%d oper:%d database:'%s' table:'%s' 'field#:'%d' where#:'%d' order:'%s' value#:%d extra_ops#:%d\n", (*action)->query_no, (*action)->operation, (*action)->db_url, (*action)->table.s, (*action)->field_count, (*action)->where_count, (*action)->order.s, (*action)->value_count, (*action)->extra_ops_count); } return 0; }
int main(int argc, char** argv) { const char* secret; secret = getenv("TRACKING_SECRET"); if (!secret) { fprintf(stderr, "TRACKING_SECRET not set\n"); return 1; } char input_line[MAX_LINE]; char plaintext[MAX_LINE]; const EVP_CIPHER* cipher = EVP_aes_128_cbc(); while (fgets(input_line, MAX_LINE, stdin) != NULL) { /* split the line into unique_id and query */ char *unique_id, *query; split_fields(input_line, &unique_id, &query, NO_MORE_FIELDS); /* parse the query string to get the value we need */ char *blob = NULL; char *key, *value; while (parse_query_param(&query, &key, &value) >= 0) { if (strcmp(key, "v") == 0) { blob = value; break; } } if (blob == NULL) continue; /* undo url encoding on the query string */ int b64_size = url_decode(blob); if (b64_size < 0) continue; /* split off the initialization vector from the actual ciphertext */ char *initialization_vector, *ciphertext; initialization_vector = blob; initialization_vector[KEY_SIZE] = '\0'; ciphertext = blob + 32; b64_size -= 32; /* base 64 decode and decrypt the ciphertext */ BIO* bio = BIO_new_mem_buf(ciphertext, b64_size); BIO* b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bio = BIO_push(b64, bio); BIO* aes = BIO_new(BIO_f_cipher()); BIO_set_cipher( aes, cipher, (unsigned char*)secret, (unsigned char*)initialization_vector, 0 /* decryption */ ); bio = BIO_push(aes, bio); /* stream the output through the filters */ int plaintext_length = BIO_read(bio, plaintext, b64_size); plaintext[plaintext_length] = '\0'; if (!BIO_get_cipher_status(bio)) { BIO_free_all(bio); continue; } /* clean up */ BIO_free_all(bio); /* check that the plaintext isn't garbage; if there are * non-ascii characters in it, it's likely bad */ bool non_ascii_junk = false; for (unsigned char* c = (unsigned char*)plaintext; *c != '\0'; c++) { if (*c > 0x7F) { non_ascii_junk = true; break; } } if (non_ascii_junk) { continue; } /* write out the unique id since we don't need it for ourselves */ fputs(unique_id, stdout); /* split the fields out of the plaintext */ char* current_string = plaintext; int field_index = 0; for (int i = 0; i < plaintext_length; i++) { char *c = plaintext + i; if (*c != '|') continue; *c = '\0'; switch (field_index) { case FIELD_USER: /* we don't use the username; skip it */ break; case FIELD_SRPATH: fputc('\t', stdout); fputs(current_string, stdout); fputc('\t', stdout); for (char* c2=current_string; *c2 != '\0'; c2++) { if (*c2 == '-') { *c2 = '\0'; break; } } fputs(current_string, stdout); break; case FIELD_LANG: fputc('\t', stdout); for (char* c2=current_string; *c2 != '\0'; c2++) { *c2 = tolower(*c2); } fputs(current_string, stdout); break; case FIELD_CNAME: assert(0!=1); } current_string = c + 1; field_index += 1; } if (field_index < FIELD_COUNT) { fputc('\t', stdout); fputs(current_string, stdout); field_index += 1; } for (; field_index < FIELD_COUNT; field_index++) fputc('\t', stdout); /* all done! */ fputc('\n', stdout); } return 0; }
int main(int argc, char** argv) { const char* secret; secret = getenv("TRACKING_SECRET"); if (!secret) { fprintf(stderr, "TRACKING_SECRET not set\n"); return 1; } char input_line[MAX_LINE]; unsigned int hash_length = SHA_DIGEST_LENGTH; unsigned char input_hash[hash_length]; unsigned char expected_hash[hash_length]; int secret_length = strlen(secret); while (fgets(input_line, MAX_LINE, stdin) != NULL) { /* get the fields */ char *ip, *path, *query, *unique_id; split_fields( input_line, &ip, &path, &query, &unique_id, NO_MORE_FIELDS ); /* in the query string, grab the fields we want to verify */ char *id = NULL; char *hash = NULL; char *url = NULL; char *key, *value; while (parse_query_param(&query, &key, &value) >= 0) { if (strcmp(key, "id") == 0) { id = value; } else if (strcmp(key, "hash") == 0) { hash = value; } else if (strcmp(key, "url") == 0) { url = value; } } if (id == NULL || hash == NULL) continue; /* decode the params */ int id_length = url_decode(id); if (id_length < 0) continue; if (url_decode(hash) != 40) continue; int url_length = 0; if (url != NULL) { url_length = url_decode(url); if (url_length < 0) continue; } /* turn the expected hash into bytes */ bool bad_hash = false; for (int i = 0; i < hash_length; i++) { int count = sscanf(&hash[i*2], "%2hhx", &input_hash[i]); if (count != 1) { bad_hash = true; break; } } if (bad_hash) continue; /* generate the expected hash */ HMAC_CTX ctx; // NOTE: EMR has openssl <1.0, so these HMAC methods don't return // error codes -- see https://www.openssl.org/docs/crypto/hmac.html HMAC_Init(&ctx, secret, secret_length, EVP_sha1()); if (strcmp("/click", path) == 0 && url != NULL) { /* the url is only for click hashes */ HMAC_Update(&ctx, url, url_length); } HMAC_Update(&ctx, id, id_length); HMAC_Final(&ctx, expected_hash, &hash_length); /* generate the old ip hash */ SHA_CTX ctx_old; int result_old = 0; unsigned char expected_hash_old[SHA_DIGEST_LENGTH]; result_old = SHA1_Init(&ctx_old); if (result_old == 0) continue; if (strcmp("/pixel/of_defenestration.png", path) != 0) { /* the IP is not included on adframe tracker hashes */ result_old = SHA1_Update(&ctx_old, ip, strlen(ip)); if (result_old == 0) continue; } result_old = SHA1_Update(&ctx_old, id, id_length); if (result_old == 0) continue; result_old = SHA1_Update(&ctx_old, secret, secret_length); if (result_old == 0) continue; result_old = SHA1_Final(expected_hash_old, &ctx_old); if (result_old == 0) continue; /* check that the hashes match */ if (memcmp(input_hash, expected_hash, SHA_DIGEST_LENGTH) != 0 && memcmp(input_hash, expected_hash_old, SHA_DIGEST_LENGTH) != 0) continue; /* split out the fullname and subreddit if necessary */ char *fullname = id; char *subreddit = NULL; for (char *c = id; *c != '\0'; c++) { if (*c == '-') { subreddit = c + 1; *c = '\0'; break; } } /* output stuff! */ fputs(unique_id, stdout); fputc('\t', stdout); fputs(path, stdout); fputc('\t', stdout); fputs(fullname, stdout); fputc('\t', stdout); if (subreddit != NULL) { fputs(subreddit, stdout); } fputc('\n', stdout); } }