/** * Handles an ABORT message from a server */ void handle_abort(struct group_list_t *group, const unsigned char *message, unsigned meslen) { const struct abort_h *abort_hdr; int found; abort_hdr = (const struct abort_h *)message; if (meslen < (abort_hdr->hlen * 4U) || ((abort_hdr->hlen * 4U) < sizeof(struct abort_h))) { glog1(group, "Rejecting ABORT from server: invalid message size"); return; } found = 0; if (abort_hdr->host == 0) { if (((abort_hdr->flags & FLAG_CURRENT_FILE) != 0) && (group->phase == PHASE_MIDGROUP)) { found = 0; } else { found = 1; } } else if (abort_hdr->host == uid) { found = 1; } if (found) { glog1(group, "Transfer aborted by server: %s", abort_hdr->message); flush_disk_cache(group); file_cleanup(group, 1); } }
/** * Verifies the data in a CLIENT_KEY message signed by the client's public key */ int verify_client_key(struct pr_group_list_t *group, int hostidx) { uint8_t *verifydata; int verifylen; struct pr_destinfo_t *dest; dest = &group->destinfo[hostidx]; // build_verify_data should never fail in this case verifydata = build_verify_data(group, hostidx, &verifylen, 0); if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (!verify_RSA_sig(dest->pubkey.rsa, group->hashtype, verifydata, verifylen, dest->verifydata, dest->verifylen)) { glog1(group, "Rejecting CLIENT_KEY from %s: verify data mismatch", dest->name); free(verifydata); return 0; } } else { if (!verify_ECDSA_sig(dest->pubkey.ec, group->hashtype, verifydata, verifylen, dest->verifydata, dest->verifylen)) { glog1(group, "Rejecting CLIENT_KEY from %s: verify data mismatch", dest->name); free(verifydata); return 0; } } free(verifydata); return 1; }
/** * Processes encryption key information received in a REGISTER message */ int handle_register_keys(const struct register_h *reg, const unsigned char *enckey, struct pr_group_list_t *group, int hostidx, uint32_t src) { unsigned char premaster[PUBKEY_LEN]; unsigned int len; struct pr_destinfo_t *dest; dest = &group->destinfo[hostidx]; memcpy(dest->rand2, reg->rand2, sizeof(dest->rand2)); if (group->keyextype == KEYEX_RSA) { if (!RSA_decrypt(group->proxy_privkey.rsa, enckey, ntohs(reg->keyinfo_len), premaster, &len)) { glog1(group, "Rejecting REGISTER from %s: " "failed to decrypt premaster secret", dest->name); return 0; } if (len != MASTER_LEN) { glog1(group, "Rejecting REGISTER from %s: " "premaster secret wrong length", dest->name); return 0; } } else { if (!import_EC_key(&dest->dhkey.ec, enckey, ntohs(reg->keyinfo_len), 1)) { glog1(group, "Rejecting REGISTER from %s: " "failed to import ECDH key", dest->name); return 0; } if (get_EC_curve(dest->dhkey.ec) != get_EC_curve(group->proxy_dhkey.ec)) { glog1(group, "Rejecting REGISTER from %s: " "invalid curve for ECDH", dest->name); return 0; } if (!get_ECDH_key(dest->dhkey.ec, group->proxy_dhkey.ec, premaster, &len)) { glog1(group, "Rejecting REGISTER from %s: " "failed to calculate premaster secret", dest->name); return 0; } } memcpy(dest->premaster, premaster, len); dest->premaster_len = len; calculate_client_keys(group, hostidx); if (dest->pubkey.key) { if (!verify_client_key(group, hostidx)) { return 0; } } return 1; }
/** * Handles an incoming KEYINFO_ACK message from a client */ void handle_keyinfo_ack(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen) { const struct keyinfoack_h *keyinfoack; unsigned char *verifydata, *verify_hash, *verify_test; int verifylen, len, dupmsg; unsigned int hashlen; struct pr_destinfo_t *dest; keyinfoack = (const struct keyinfoack_h *)message; dest = &group->destinfo[hostidx]; if ((meslen < (keyinfoack->hlen * 4U)) || ((keyinfoack->hlen * 4U) < sizeof(struct keyinfoack_h))) { glog1(group, "Rejecting KEYINFO_ACK from %s: invalid message size", dest->name); send_downstream_abort(group, dest->id, "Invalid message size", 0); return; } if (!(verifydata = build_verify_data(group, hostidx, &verifylen,1))) { glog1(group, "Rejecting KEYINFO_ACK from %s: " "error exporting client public key", dest->name); return; } verify_hash = safe_calloc(group->hmaclen, 1); verify_test = safe_calloc(VERIFY_LEN + group->hmaclen, 1); hash(group->hashtype, verifydata, verifylen, verify_hash, &hashlen); PRF(group->hashtype, VERIFY_LEN, group->groupmaster, sizeof(group->groupmaster), "client finished", verify_hash, hashlen, verify_test, &len); if (memcmp(keyinfoack->verify_data, verify_test, VERIFY_LEN)) { glog1(group, "Rejecting KEYINFO_ACK from %s: verify data mismatch", dest->name); free(verifydata); free(verify_hash); free(verify_test); return; } free(verifydata); free(verify_hash); free(verify_test); dupmsg = (dest->state == PR_CLIENT_READY); glog2(group, "Received KEYINFO_ACK%s from %s", dupmsg ? "+" : "", dest->name); dest->state = PR_CLIENT_READY; if (!check_unfinished_clients(group, 0)) { group->phase = PR_PHASE_RECEIVING; } }
/** * Handles an incoming REGSITER message from a client. */ void handle_register(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen, uint32_t src) { const struct register_h *reg; const unsigned char *enckey; struct pr_destinfo_t *dest; int dupmsg; reg = (const struct register_h *)message; enckey = (const unsigned char *)reg + sizeof(struct register_h); if (group->destcount == MAXPROXYDEST) { glog1(group, "Rejecting REGISTER from %08X: max destinations exceeded", ntohl(src)); send_downstream_abort(group, src, "Max destinations exceeded", 0); return; } if ((meslen < (reg->hlen * 4U)) || ((reg->hlen * 4U) < sizeof(struct register_h) + ntohs(reg->keyinfo_len))) { glog1(group, "Rejecting REGISTER from %08X: invalid message size", ntohl(src)); send_downstream_abort(group, src, "Invalid message size", 0); return; } if (hostidx == -1) { hostidx = add_client(src, group); } dest = &group->destinfo[hostidx]; dupmsg = (dest->registered == 1); dest->registered = 1; dest->regtime.tv_sec = ntohl(reg->tstamp_sec); dest->regtime.tv_usec = ntohl(reg->tstamp_usec); if (dest->state != PR_CLIENT_REGISTERED) { if (group->keytype != KEY_NONE) { if (!handle_register_keys(reg, enckey, group, hostidx, src)) { return; } } if (!group->client_auth || dest->pubkey.key) { dest->state = PR_CLIENT_REGISTERED; } } glog2(group, "Received REGISTER%s from %s", dupmsg ? "+" : "", dest->name); if (dest->state == PR_CLIENT_REGISTERED) { check_pending(group, hostidx, message); } }
/** * Sends a pending aggregate message for a given group and message */ void send_pending(struct pr_group_list_t *group, int pendidx) { switch (group->pending[pendidx].msg) { case REGISTER: send_register(group, pendidx); break; case FILEINFO_ACK: send_fileinfo_ack(group, pendidx); break; case STATUS: send_status(group, pendidx); break; case COMPLETE: send_complete(group, pendidx); break; default: glog1(group, "Tried to send pending on invalid type %s", func_name(group->pending[pendidx].msg)); return; } if ((group->pending[pendidx].count <= 0) || (group->pending[pendidx].msg == STATUS)) { // Finish the cleanup we started in load_pending // Always do this for a STATUS, since we don't have a pending list free(group->pending[pendidx].naklist); memset(&group->pending[pendidx], 0, sizeof(struct pr_pending_info_t)); } }
/** * Processes an incoming REG_CONF message. * Expected in response to a REGISTER when encryption is disabled. */ void handle_regconf(struct group_list_t *group, const unsigned char *message, unsigned meslen) { const struct regconf_h *regconf; const uint32_t *addrlist; int addrcnt; regconf = (const struct regconf_h *)message; addrlist = (const uint32_t *)(message + (regconf->hlen * 4)); if ((meslen < (regconf->hlen * 4U)) || ((regconf->hlen * 4U) < sizeof(struct regconf_h))) { glog1(group, "Rejecting REG_CONF from server: invalid message size"); return; } addrcnt = (meslen - (regconf->hlen * 4)) / 4; if (uid_in_list(addrlist, addrcnt)) { glog2(group, "Registration confirmed"); group->phase = PHASE_MIDGROUP; set_timeout(group, 0); } if (group->restart) { read_restart_file(group); } }
/** * Handles an ABORT message from a client or server * and forwards if necessary. */ void handle_abort(struct pr_group_list_t *group, const union sockaddr_u *src, const unsigned char *message, unsigned meslen, uint32_t src_id) { const struct abort_h *abort_hdr; int upstream, hostidx, current; abort_hdr = (const struct abort_h *)message; upstream = (addr_equal(&group->up_addr, src)); if (meslen < (abort_hdr->hlen * 4U) || ((abort_hdr->hlen * 4U) < sizeof(struct abort_h))) { glog1(group, "Rejecting ABORT from %s: invalid message size", upstream ? "server" : "client"); } if (upstream) { if ((abort_hdr->host == 0) || abort_hdr->host == uid ) { glog1(group, "Transfer aborted by server: %s", abort_hdr->message); current = ((abort_hdr->flags & FLAG_CURRENT_FILE) != 0); if (proxy_type != RESPONSE_PROXY) { send_downstream_abort(group, 0, abort_hdr->message, current); } if (!current) { group_cleanup(group); } } else { if (proxy_type != RESPONSE_PROXY) { send_downstream_abort(group, abort_hdr->host, abort_hdr->message, 0); } } } else { if ((hostidx = find_client(group, src_id)) != -1) { glog1(group, "Transfer aborted by %s: %s", group->destinfo[hostidx].name, abort_hdr->message); } else { glog1(group, "Transfer aborted by %08X: %s", ntohl(src_id), abort_hdr->message); } send_upstream_abort(group, src_id, abort_hdr->message); } }
/** * Read in the contents of a FILEINFO message * Returns 1 on success, 0 on error or ignore */ int read_fileinfo(struct group_list_t *group, const unsigned char *message, int meslen, struct timeval rxtime) { const struct fileinfo_h *fileinfo; const uint32_t *addrlist; int listlen, maxsecsize; const char *name, *flink, *p; fileinfo = (const struct fileinfo_h *)message; addrlist = (const uint32_t *)(message + (fileinfo->hlen * 4)); name = (const char *)message + sizeof(struct fileinfo_h); flink = name + (fileinfo->namelen * 4); listlen = (meslen - (fileinfo->hlen * 4)) / 4; if ((meslen < (fileinfo->hlen * 4)) || ((fileinfo->hlen * 4) < sizeof(struct fileinfo_h)) || ((fileinfo->namelen * 4) > MAXPATHNAME) || ((fileinfo->linklen * 4) > MAXPATHNAME) || ((fileinfo->hlen * 4) != sizeof(struct fileinfo_h) + (fileinfo->namelen * 4) + (fileinfo->linklen * 4))) { glog1(group, "Rejecting FILEINFO from server: invalid message size"); send_abort(group, "Rejecting FILEINFO: invalid message size"); return 0; } if (!uid_in_list(addrlist, listlen)) { set_timeout(group, 0); return 0; } if (group->phase == PHASE_RECEIVING) { // We already got the FILEINFO, so no need to reprocess. // Just resend the INFO_ACK and reset the timeout send_fileinfo_ack(group, group->fileinfo.restart); set_timeout(group, 0); return 0; } if ((group->phase == PHASE_MIDGROUP) && (group->file_id == ntohs(fileinfo->file_id))) { // We already got the FILEINFO, and it's for a completed file. // So resend the COMPLETE and reset the timeout send_complete(group, (fileinfo->ftype == FTYPE_FREESPACE)); set_timeout(group, 0); return 0; } // Load fileinfo params into list memset(&group->fileinfo, 0, sizeof(struct file_t)); group->fileinfo.ftype = fileinfo->ftype; group->file_id = ntohs(fileinfo->file_id); strncpy(group->fileinfo.name, name, fileinfo->namelen * 4); strncpy(group->fileinfo.linkname, flink, fileinfo->linklen * 4); group->fileinfo.size = (f_offset_t)ntohs(fileinfo->hifsize) << 32; group->fileinfo.size |= ntohl(fileinfo->lofsize); if (group->fileinfo.size) { maxsecsize = (group->blocksize * 8 > MAXSECTION ? MAXSECTION : group->blocksize * 8); group->fileinfo.blocks = (int32_t)((group->fileinfo.size / group->blocksize) + (group->fileinfo.size % group->blocksize ? 1 : 0)); group->fileinfo.sections = (group->fileinfo.blocks / maxsecsize) + (group->fileinfo.blocks % maxsecsize ? 1 : 0); group->fileinfo.secsize_small = group->fileinfo.blocks / group->fileinfo.sections; group->fileinfo.secsize_big = group->fileinfo.secsize_small + (group->fileinfo.blocks % group->fileinfo.sections ? 1 : 0); group->fileinfo.big_sections = group->fileinfo.blocks - (group->fileinfo.secsize_small * group->fileinfo.sections); } else { group->fileinfo.blocks = 0; group->fileinfo.sections = 0; group->fileinfo.secsize_small = 0; group->fileinfo.secsize_big = 0; group->fileinfo.big_sections = 0; } group->fileinfo.tstamp = ntohl(fileinfo->ftstamp); group->last_server_ts.tv_sec = ntohl(fileinfo->tstamp_sec); group->last_server_ts.tv_usec = ntohl(fileinfo->tstamp_usec); group->last_server_rx_ts = rxtime; group->fileinfo.fd = -1; // Run some checks on the filename if (strlen(group->fileinfo.name) == 0) { glog1(group, "Rejecting FILEINFO from server: blank file name"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } p = strstr(group->fileinfo.name, ".."); if ((p != NULL) && ((p[2] == '\x0') || (p[2] == '/') || (p[2] == '\\')) && ((p == group->fileinfo.name) || (p[-1] == '/') || (p[-1] == '\\'))) { glog1(group, "Rejecting FILEINFO from server: filename contains .."); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } if (fileinfo->ftype == FTYPE_LINK) { if (strlen(group->fileinfo.linkname) == 0) { glog1(group, "Rejecting FILEINFO from server: blank link name"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } } return 1; }
/** * Process an incoming FILEINFO message. * Expected in the middle of a group with no current file. */ void handle_fileinfo(struct group_list_t *group, const unsigned char *message, unsigned meslen, struct timeval rxtime) { stat_struct statbuf; int found_dir; if (!read_fileinfo(group, message, meslen, rxtime)) { return; } glog2(group, "Name of file to receive: %s", group->fileinfo.name); switch (group->fileinfo.ftype) { case FTYPE_REG: glog2(group, "Bytes: %s, Blocks: %d, Sections: %d", printll(group->fileinfo.size), group->fileinfo.blocks, group->fileinfo.sections); glog3(group, "small section size: %d, " "big section size: %d, # big sections: %d", group->fileinfo.secsize_small, group->fileinfo.secsize_big, group->fileinfo.big_sections); break; case FTYPE_DIR: glog2(group, "Empty directory"); break; case FTYPE_LINK: glog2(group, "Symbolic link to %s", group->fileinfo.linkname); break; case FTYPE_DELETE: glog2(group, "Deleting file/directory"); break; case FTYPE_FREESPACE: glog2(group, "Get free space for path"); break; default: glog1(group, "Invalid file type: %d", group->fileinfo.ftype); send_abort(group, "Invalid file type"); return; } if (!setup_dest_file(group)) { // A rejected file is still a success because we responded with a // COMPLETE with status=rejected instead of with an ABORT return; } // Make sure the path to the destination file exists and // remove or back up any existing file if (!create_path_to_file(group, group->fileinfo.filepath)) { glog0(group, "Error creating path to data file"); early_complete(group, COMP_STAT_REJECTED, 0); return; } found_dir = 0; if (tempfile && !group->sync_preview) { clear_path(group->fileinfo.temppath, group); } if ((group->fileinfo.ftype != FTYPE_DELETE) || (group->fileinfo.ftype != FTYPE_FREESPACE)) { // Don't do path checks for metafile commands } else if (lstat_func(group->fileinfo.filepath, &statbuf) != -1) { glog3(group, "checking existing file"); if ((group->fileinfo.ftype != FTYPE_DIR) || !S_ISDIR(statbuf.st_mode)) { if ((group->fileinfo.ftype != FTYPE_REG) || !S_ISREG(statbuf.st_mode) || ((!group->restart) && (!group->sync_mode))) { // Don't clear/backup if we're receiving a regular file // and we're in either restart mode or sync mode glog3(group, "calling move_to_backup"); if (!tempfile) { move_to_backup(group); } } } else { glog3(group, "found dir"); found_dir = 1; } } else if (errno != ENOENT) { gsyserror(group, "Error checking file %s",group->fileinfo.filepath); } switch (group->fileinfo.ftype) { case FTYPE_REG: handle_fileinfo_regular(group); break; case FTYPE_DIR: handle_fileinfo_dir(group, found_dir); break; case FTYPE_LINK: handle_fileinfo_link(group); break; case FTYPE_DELETE: handle_fileinfo_delete(group); break; case FTYPE_FREESPACE: handle_fileinfo_freespace(group); break; default: glog0(group, "Error handling FILEINFO: shouldn't get here!"); } }
/** * Validate and establish the destination name of an incoming file. * Returns 0 if the file was rejected for some reason, 1 otherwise. */ int setup_dest_file(struct group_list_t *group) { int found_dest_dir, len, i; int (*cmp)(const char *, const char *); int (*ncmp)(const char *, const char *, size_t); #if PATH_SEP != '/' // First translate any '/' in the sent file name to PATH_SEP { char *p; while ((p = strchr(group->fileinfo.name, '/')) != NULL) { *p = PATH_SEP; } } #endif #ifdef WINDOWS cmp = stricmp; ncmp = strnicmp; #else cmp = strcmp; ncmp = strncmp; #endif if (isfullpath(group->fileinfo.name)) { if (strcmp(tempdir, "")) { glog1(group, "Rejecting file with absolute pathname: " "temp directory is in use"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } for (found_dest_dir = 0, i = 0; i < destdircnt; i++) { if (!ncmp(group->fileinfo.name, destdir[i], strlen(destdir[i]))) { if (!cmp(group->fileinfo.name, destdir[i])) { glog1(group, "Rejecting file with absolute pathname: " "can't have the same name as a dest directory"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } else { found_dest_dir = 1; break; } } } if (!found_dest_dir) { glog1(group, "Rejecting file with absolute pathname: " "doesn't match any dest directory"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } group->fileinfo.destdiridx = i; snprintf(group->fileinfo.filepath, sizeof(group->fileinfo.filepath), "%s", group->fileinfo.name); } else { if (!strcmp(tempdir, "")) { len = snprintf(group->fileinfo.filepath, sizeof(group->fileinfo.filepath), "%s%c%s", destdir[0], PATH_SEP, group->fileinfo.name); } else { len = snprintf(group->fileinfo.filepath, sizeof(group->fileinfo.filepath), "%s%c_group_%08X%c%s", tempdir, PATH_SEP, group->group_id, PATH_SEP, group->fileinfo.name); } if (len >= sizeof(group->fileinfo.filepath)) { glog1(group, "Rejecting file: max pathname length exceeded"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } } len = snprintf(group->fileinfo.temppath, sizeof(group->fileinfo.temppath), "%s.~uftp-%08X-%04X", group->fileinfo.filepath, group->group_id, group->file_id); if (len >= sizeof(group->fileinfo.temppath)) { glog1(group, "Rejecting file: max pathname length exceeded"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } return 1; }
/** * Gets the current timeout value to use for the main loop * * First check to see if any active groups have an expired timeout, and * handle that timeout. Once all expired timeouts have been handled, find * the active group with the earliest timeout and return the time until that * timeout. If there are no active groups, return NULL. */ struct timeval *getrecenttimeout(void) { static struct timeval tv = {0,0}; struct timeval current_timestamp, min_timestamp; int i, found_timeout, done, sent_naks; struct group_list_t *group; unsigned int section, nak_count; unsigned char *naks; gettimeofday(¤t_timestamp, NULL); done = 0; while (!done) { found_timeout = 0; done = 1; for (i = 0; i < MAXLIST; i++) { group = &group_list[i]; if (group->group_id != 0) { if (cmptimestamp(current_timestamp, group->timeout_time) >= 0) { switch (group->phase) { case PHASE_REGISTERED: send_register(group); break; case PHASE_RECEIVING: case PHASE_MIDGROUP: glog1(group, "Transfer timed out"); send_abort(group, "Transfer timed out"); break; case PHASE_COMPLETE: send_complete(group, 0); break; } done = 0; } else if ((!found_timeout) || (cmptimestamp(group->timeout_time, min_timestamp) < 0)) { glog5(group, "found min timeout time: %d:%06d", group->timeout_time.tv_sec, group->timeout_time.tv_usec); min_timestamp = group->timeout_time; found_timeout = 1; } // Check for a NAK timeout for sending a STATUS or COMPLETE if ((group->fileinfo.nak_time.tv_sec != 0) && cmptimestamp(current_timestamp, group->fileinfo.nak_time) >= 0) { group->fileinfo.nak_time.tv_sec = 0; group->fileinfo.nak_time.tv_usec = 0; // Send NAKs sent_naks = 0; retry_naks: for (section = group->fileinfo.nak_section_first; section < group->fileinfo.nak_section_last; section++) { naks = NULL; nak_count = get_naks(group, section, &naks); glog3(group, "read %d NAKs for section %d", nak_count, section); if (nak_count > 0) { send_status(group, section, naks, nak_count); sent_naks = 1; } free(naks); naks = NULL; } if (file_done(group, 1)) { glog2(group, "File transfer complete"); send_complete(group, 0); file_cleanup(group, 0); } else if (group->fileinfo.got_done && !sent_naks) { // We didn't send any NAKs since the last time // but the server is asking for some, // so check all prior sections group->fileinfo.nak_section_last = group->fileinfo.nak_section_first; group->fileinfo.nak_section_first = 0; group->fileinfo.got_done = 0; goto retry_naks; } } else if ((group->fileinfo.nak_time.tv_sec != 0) && ((!found_timeout) || (cmptimestamp(group->fileinfo.nak_time, min_timestamp) < 0))) { glog5(group, "found min nak time: %d:%06d", group->fileinfo.nak_time.tv_sec, group->fileinfo.nak_time.tv_usec); min_timestamp = group->fileinfo.nak_time; found_timeout = 1; } // Check congestion control feedback timer if (!group->isclr) { if ((group->cc_time.tv_sec != 0) && (cmptimestamp(current_timestamp, group->cc_time) >= 0)) { send_cc_ack(group); } else if ((group->cc_time.tv_sec != 0) && ((!found_timeout) || (cmptimestamp(group->cc_time, min_timestamp) < 0))) { glog5(group, "found min CC time: %d:%06d", group->cc_time.tv_sec, group->cc_time.tv_usec); min_timestamp = group->cc_time; found_timeout = 1; } } } } // Check timeout for proxy key request if (has_proxy && (proxy_pubkey.key == 0)) { if (cmptimestamp(current_timestamp, next_keyreq_time) >= 0) { send_key_req(); done = 0; } else if ((!found_timeout) || (cmptimestamp(next_keyreq_time, min_timestamp) < 0)) { min_timestamp = next_keyreq_time; found_timeout = 1; } } // Check timeout for sending heartbeat if (hbhost_count) { if (cmptimestamp(current_timestamp, next_hb_time) >= 0) { send_hb_request(listener, hb_hosts, hbhost_count, &next_hb_time, hb_interval, uid); done = 0; } else if ((!found_timeout) || (cmptimestamp(next_hb_time, min_timestamp) < 0)) { min_timestamp = next_hb_time; found_timeout = 1; } } } if (found_timeout) { tv = diff_timeval(min_timestamp, current_timestamp); return &tv; } else { return NULL; } }
/** * This is the main message reading loop. Messages are read, validated, * decrypted if necessary, then passed to the appropriate routine for handling. */ void mainloop(void) { struct uftp_h *header; struct group_list_t *group; unsigned char *buf, *decrypted, *message; char rxname[INET6_ADDRSTRLEN]; unsigned int decryptlen, meslen; int packetlen, rval, i, ecn; uint8_t version, *func, tos; uint16_t txseq; union sockaddr_u src; struct timeval *tv, rxtime; double new_grtt; log2(0, 0, 0, "%s", VERSIONSTR); for (i = 0; i < key_count; i++) { if (privkey_type[i] == KEYBLOB_RSA) { log2(0, 0, 0, "Loaded %d bit RSA key with fingerprint %s", RSA_keylen(privkey[i].rsa) * 8, print_key_fingerprint(privkey[i], KEYBLOB_RSA)); } else { log2(0, 0, 0, "Loaded ECDSA key with curve %s and fingerprint %s", curve_name(get_EC_curve(privkey[i].ec)), print_key_fingerprint(privkey[i], KEYBLOB_EC)); } } buf = safe_calloc(MAXMTU, 1); decrypted = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; while (1) { tv = getrecenttimeout(); if (tv) { log5(0, 0, 0, "read timeout: %d.%06d", tv->tv_sec, tv->tv_usec); } if (read_packet(listener, &src, buf, &packetlen, MAXMTU, tv, &tos) <= 0) { continue; } gettimeofday(&rxtime, NULL); if ((rval = getnameinfo((struct sockaddr *)&src, family_len(src), rxname, sizeof(rxname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } if (header->version == UFTP_VER_NUM) { version = header->version; group = find_group(ntohl(header->group_id), header->group_inst); } else { log1(0, 0, 0, "Invalid message from %s: not uftp packet " "or invalid version", rxname); continue; } if (packetlen < sizeof(struct uftp_h) + 4) { log1(0, 0, 0, "Invalid packet size from %s: %d", rxname, packetlen); continue; } txseq = htons(header->seq); // A KEY_INFO or ABORT could come from a proxy, so don't check the seq // TODO: need to account for these in the loss history if ((group != NULL) && (header->func != KEYINFO) && (header->func != ABORT)) { if ((int16_t)(group->max_txseq - txseq) > MAXMISORDER) { glog3(group, "seq out of range, dropping"); continue; } if (group->cc_type != CC_NONE) { ecn = ((tos & 0x3) == 3); update_loss_history(group, txseq, packetlen, ecn); } else if ((int16_t)(txseq - group->max_txseq) > 0) { group->max_txseq = txseq; } } if ((header->func == ENCRYPTED) && (group != NULL) && (group->keytype != KEY_NONE)) { if (group->phase == PHASE_REGISTERED) { glog1(group, "Got encrypted packet from %s " "but keys not established", rxname); } if (!validate_and_decrypt(buf, packetlen, &decrypted, &decryptlen, group->keytype, group->groupkey, group->groupsalt, group->ivlen, group->hashtype, group->grouphmackey, group->hmaclen, group->sigtype, group->keyextype, group->server_pubkey, group->server_pubkeylen)) { glog1(group, "Rejecting message from %s: " "decrypt/validate failed", rxname); continue; } func = (uint8_t *)decrypted; message = decrypted; meslen = decryptlen; } else { if ((group != NULL) && (group->keytype != KEY_NONE) && ((header->func == FILEINFO) || (header->func == FILESEG) || (header->func == DONE) || (header->func == DONE_CONF) || ((header->func == ABORT) && (group->phase != PHASE_REGISTERED)))) { glog1(group, "Rejecting %s message from %s: not encrypted", func_name(header->func), rxname); continue; } func = (uint8_t *)&header->func; message = buf + sizeof(struct uftp_h); meslen = packetlen - sizeof(struct uftp_h); } if (group != NULL) { new_grtt = unquantize_grtt(header->grtt); if (fabs(new_grtt - group->grtt) > 0.001) { group->grtt = new_grtt; set_timeout(group, 1); } group->gsize = unquantize_gsize(header->gsize); glog5(group, "grtt: %.3f", group->grtt); } if (header->func == PROXY_KEY) { handle_proxy_key(&src, message, meslen); continue; } if (header->func == HB_RESP) { handle_hb_response(listener, &src, message, meslen, hb_hosts, hbhost_count, privkey[0], privkey_type[0], uid); continue; } if (header->func == ANNOUNCE) { // Ignore any ANNOUNCE for a group we're already handling if (group == NULL) { handle_announce(&src, buf, packetlen, rxtime); } else if (group->phase == PHASE_MIDGROUP) { // Make sure we don't time out while waiting for other // clients to register with the server. set_timeout(group, 0); } } else { if (group == NULL) { // group / file ID not in list continue; } if (group->version != version) { glog1(group, "Version mismatch"); continue; } if (group->src_id != header->src_id) { glog1(group, "Source ID mismatch"); continue; } if (*func == ABORT) { handle_abort(group, message, meslen); continue; } switch (group->phase) { case PHASE_REGISTERED: if (group->keytype != KEY_NONE) { if (*func == KEYINFO) { handle_keyinfo(group, message, meslen, header->src_id); } else { glog1(group, "Expected KEYINFO, got %s", func_name(*func)); } } else if (group->keytype == KEY_NONE) { if (*func == REG_CONF) { handle_regconf(group, message, meslen); } else if (*func == FILEINFO) { handle_fileinfo(group, message, meslen, rxtime); } else { glog1(group, "Expected REG_CONF, got %s", func_name(*func)); } } break; case PHASE_MIDGROUP: if (*func == FILEINFO) { handle_fileinfo(group, message, meslen, rxtime); } else if (*func == KEYINFO) { handle_keyinfo(group, message, meslen, header->src_id); } else if (*func == DONE) { handle_done(group, message, meslen); } else { // Other clients may be still getting earlier files or // setting up, so silently ignore anything unexpected // and reset the timeout. set_timeout(group, 0); } break; case PHASE_RECEIVING: if (*func == FILEINFO) { handle_fileinfo(group, message, meslen, rxtime); } else if (*func == FILESEG) { handle_fileseg(group, message, meslen, txseq); } else if (*func == DONE) { handle_done(group, message, meslen); } else if (*func == CONG_CTRL) { handle_cong_ctrl(group, message, meslen, rxtime); } break; case PHASE_COMPLETE: if (*func == DONE_CONF) { handle_done_conf(group, message, meslen); } break; } } } }
/** * Sends back a COMPLETE message in response to a DONE or FILEINFO */ void send_complete(struct group_list_t *group, int set_freespace) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct complete_h *complete; struct freespace_info_he *freespace; int payloadlen, enclen; struct timeval tv; gettimeofday(&tv, NULL); if ((group->phase == PHASE_COMPLETE) && (cmptimestamp(tv, group->expire_time) >= 0)) { glog1(group, "Completion unconfirmed by server"); move_files(group); file_cleanup(group, 0); return; } buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; complete = (struct complete_h *)(buf + sizeof(struct uftp_h)); freespace = (struct freespace_info_he *)((unsigned char *)complete + sizeof(struct complete_h)); set_uftp_header(header, COMPLETE, group); complete->func = COMPLETE; if (set_freespace) { complete->hlen = (sizeof(struct complete_h) + sizeof(struct freespace_info_he)) / 4; } else { complete->hlen = sizeof(struct complete_h) / 4; } complete->status = group->fileinfo.comp_status; complete->file_id = htons(group->file_id); if (set_freespace) { set_freespace_info(group, freespace); } payloadlen = complete->hlen * 4; if ((group->phase != PHASE_REGISTERED) && (group->keytype != KEY_NONE)) { encrypted = NULL; if (!encrypt_and_sign(buf, &encrypted, payloadlen, &enclen, group->keytype, group->groupkey, group->groupsalt,&group->ivctr, group->ivlen, group->hashtype, group->grouphmackey, group->hmaclen, group->sigtype, group->keyextype, group->client_privkey, group->client_privkeylen)) { glog0(group, "Error encrypting COMPLETE"); free(buf); return; } outpacket = encrypted; payloadlen = enclen; } else { encrypted = NULL; outpacket = buf; } payloadlen += sizeof(struct uftp_h); if (nb_sendto(listener, outpacket, payloadlen, 0, (struct sockaddr *)&group->replyaddr, family_len(group->replyaddr)) == SOCKET_ERROR) { gsockerror(group, "Error sending COMPLETE"); } else { glog2(group, "COMPLETE sent"); } set_timeout(group, 0); free(buf); free(encrypted); }
/** * Processes a new incoming ANNOUNCE */ void handle_announce(union sockaddr_u *src, unsigned char *packet, unsigned packetlen, struct timeval rxtime) { struct uftp_h *header; struct announce_h *announce; uint32_t *addrlist; int addrlen, rval; struct group_list_t *group; time_t t; struct tm *start_time; char privname[INET6_ADDRSTRLEN], srcname[INET6_ADDRSTRLEN]; char srcfqdn[DESTNAME_LEN]; header = (struct uftp_h *)packet; announce = (struct announce_h *)(packet + sizeof(struct uftp_h)); addrlist = (uint32_t *)((unsigned char *)announce + (announce->hlen * 4)); addrlen = (packetlen - sizeof(struct uftp_h) - (announce->hlen * 4)) / 4; if ((packetlen < sizeof(struct uftp_h) + (announce->hlen * 4U)) || ((announce->hlen * 4U) < sizeof(struct announce_h))) { log1(ntohl(header->group_id), header->group_inst, 0, "Rejecting ANNOUNCE from %08X: invalid message size", ntohl(header->src_id)); return; } if ((addrlen != 0) && (!uid_in_list(addrlist, addrlen))) { log1(ntohl(header->group_id), header->group_inst, 0, "Name not in host list"); return; } if ((group = find_open_slot()) == NULL ) { log0(ntohl(header->group_id), header->group_inst, 0, "Error: maximum number of incoming files exceeded: %d\n", MAXLIST); return; } t = time(NULL); start_time = localtime(&t); snprintf(group->start_date, sizeof(group->start_date), "%04d%02d%02d", start_time->tm_year + 1900, start_time->tm_mon + 1, start_time->tm_mday); snprintf(group->start_time, sizeof(group->start_time), "%02d%02d%02d", start_time->tm_hour, start_time->tm_min, start_time->tm_sec); if (!read_announce(group, packet, src, rxtime, packetlen)) { return; } if ((rval = getnameinfo((struct sockaddr *)src, family_len(*src), srcname, sizeof(srcname), NULL, 0, NI_NUMERICHOST)) != 0) { glog1(group, "getnameinfo failed: %s", gai_strerror(rval)); } if (!noname) { if ((rval = getnameinfo((struct sockaddr *)src, family_len(*src), srcfqdn, sizeof(srcfqdn), NULL, 0, 0)) != 0) { glog1(group, "getnameinfo failed: %s", gai_strerror(rval)); } } else { strncpy(srcfqdn, srcname, sizeof(srcfqdn) - 1); } if ((rval = getnameinfo((struct sockaddr *)&group->multi, family_len(group->multi), privname, sizeof(privname), NULL, 0, NI_NUMERICHOST)) != 0) { glog1(group, "getnameinfo failed: %s", gai_strerror(rval)); } glog2(group, "Received request from %08X at %s (%s)", ntohl(group->src_id), srcfqdn, srcname); glog2(group, "Using private multicast address %s", privname); glog3(group, "grtt = %.6f", group->grtt); glog3(group, "send time: %d.%06d", group->last_server_ts.tv_sec, group->last_server_ts.tv_usec); glog3(group, "receive time: %d.%06d", group->last_server_rx_ts.tv_sec, group->last_server_rx_ts.tv_usec); if (status_file) { fprintf(status_file, "CONNECT;%04d/%02d/%02d-%02d:%02d:%02d;%08X;%08X;%s;%s\n", start_time->tm_year + 1900, start_time->tm_mon + 1, start_time->tm_mday, start_time->tm_hour, start_time->tm_min, start_time->tm_sec, ntohl(group->src_id), group->group_id, srcname, srcfqdn); fflush(status_file); } if (group->restart) { if (group->sync_mode) { glog1(group, "Sync mode and restart mode incompatible"); send_abort(group, "Sync mode and restart mode incompatible"); return; } } if (!addr_blank(&group->multi)) { if (server_count > 0) { if (!is_multicast(&group->multi, 1)) { glog1(group, "Invalid source specific multicast address: %s", privname); send_abort(group, "Invalid source specific multicast address"); return; } if (!other_mcast_users(group)) { if (!multicast_join(listener, group->group_id, &group->multi, m_interface, interface_count, server_keys, server_count)) { send_abort(group, "Error joining multicast group"); return; } if (has_proxy) { if (!multicast_join(listener,group->group_id, &group->multi, m_interface, interface_count, &proxy_info, 1)) { send_abort(group, "Error joining multicast group"); return; } } } } else { if (!is_multicast(&group->multi, 0)) { glog1(group, "Invalid multicast address: %s", privname); send_abort(group, "Invalid multicast address"); return; } if (!other_mcast_users(group)) { if (!multicast_join(listener, group->group_id, &group->multi, m_interface, interface_count, NULL, 0)) { send_abort(group, "Error joining multicast group"); return; } } } group->multi_join = 1; } send_register(group); }
/** * Read in the contents of an ANNOUNCE. */ int read_announce(struct group_list_t *group, unsigned char *packet, union sockaddr_u *src, struct timeval rxtime, int packetlen) { struct uftp_h *header; struct announce_h *announce; struct enc_info_he *encinfo; uint8_t *publicmcast, *privatemcast; uint8_t *he; unsigned int iplen, extlen; header = (struct uftp_h *)packet; announce = (struct announce_h *)(packet + sizeof(struct uftp_h)); encinfo = NULL; group->phase = PHASE_REGISTERED; group->version = header->version; group->group_id = ntohl(header->group_id); group->group_inst = header->group_inst; group->src_id = header->src_id; if (has_proxy) { group->replyaddr = proxy_info.addr; } else { group->replyaddr = *src; } group->grtt = unquantize_grtt(header->grtt); group->rtt = 0; group->robust = announce->robust; group->cc_type = announce->cc_type; group->gsize = unquantize_gsize(header->gsize); group->blocksize = ntohs(announce->blocksize); group->last_server_ts.tv_sec = ntohl(announce->tstamp_sec); group->last_server_ts.tv_usec = ntohl(announce->tstamp_usec); group->last_server_rx_ts = rxtime; group->restart = ((group->group_inst != 0) && (strcmp(tempdir, ""))); group->sync_preview = ((announce->flags & FLAG_SYNC_PREVIEW) != 0); group->sync_mode = group->sync_preview || ((announce->flags & FLAG_SYNC_MODE) != 0); iplen = ((announce->flags & FLAG_IPV6) != 0) ? sizeof(struct in6_addr) : sizeof(struct in_addr); publicmcast = ((uint8_t *)announce) + sizeof(struct announce_h); privatemcast = publicmcast + iplen; if ((announce->flags & FLAG_IPV6) != 0) { group->multi.sin6.sin6_family = AF_INET6; #ifdef SOCKADDR_LEN group->multi.sin6.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&group->multi.sin6.sin6_addr.s6_addr, privatemcast, iplen); } else { group->multi.sin.sin_family = AF_INET; #ifdef SOCKADDR_LEN group->multi.sin.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&group->multi.sin.sin_addr.s_addr, privatemcast, iplen); } group->fileinfo.fd = -1; if ((announce->hlen * 4U) < sizeof(struct announce_h) + (2U * iplen)) { glog1(group, "Rejecting ANNOUNCE from %08X: invalid header size", ntohl(group->src_id)); send_abort(group, "Invalid header size"); return 0; } if ((announce->hlen * 4U) > sizeof(struct announce_h) + (2U * iplen)) { he = (unsigned char *)announce + sizeof(struct announce_h) + (2U * iplen); if (*he == EXT_ENC_INFO) { encinfo = (struct enc_info_he *)he; extlen = encinfo->extlen * 4U; if ((extlen > ((announce->hlen * 4U) - sizeof(struct announce_h))) || (extlen < sizeof(struct enc_info_he)) || (extlen != (sizeof(struct enc_info_he) + ntohs(encinfo->keylen) + ntohs(encinfo->dhlen) + ntohs(encinfo->siglen)))) { glog1(group, "Rejecting ANNOUNCE from %08X: " "invalid extension size", ntohl(group->src_id)); send_abort(group, "Invalid extension size"); return 0; } } } if (encinfo != NULL) { if (!read_announce_encryption(group, encinfo, packet, packetlen)) { return 0; } } else if (encrypted_only) { glog1(group, "No unencrypted transfers allowed"); send_abort(group, "No unencrypted transfers allowed"); return 0; } else { group->keyextype = KEYEX_NONE; group->keytype = KEY_NONE; group->hashtype = HASH_NONE; group->sigtype = SIG_NONE; group->client_auth = 0; } gettimeofday(&group->expire_time, NULL); if (4 * group->robust * group->grtt < 1.0) { add_timeval_d(&group->expire_time, 1.0); } else { add_timeval_d(&group->expire_time, 4 * group->robust * group->grtt); } group->fileinfo.nak_time.tv_sec = 0; group->fileinfo.nak_time.tv_usec = 0; // Size of data packet, used in transmission speed calculations group->datapacketsize = group->blocksize + sizeof(struct fileseg_h); if (group->cc_type == CC_TFMCC) { group->datapacketsize += sizeof(struct tfmcc_data_info_he); } if (group->keytype != KEY_NONE) { group->datapacketsize += ((group->sigtype == SIG_KEYEX) ? group->server_pubkeylen : (group->sigtype == SIG_HMAC) ? group->hmaclen : 0) + KEYBLSIZE + sizeof(struct encrypted_h); } // 8 = UDP size, 20 = IPv4 size, 40 = IPv6 size if ((announce->flags & FLAG_IPV6) != 0) { group->datapacketsize += sizeof(struct uftp_h) + 8 + 40; } else { group->datapacketsize += sizeof(struct uftp_h) + 8 + 20; } if (group->cc_type != CC_NONE) { group->loss_history= safe_calloc(0x10000,sizeof(struct loss_history_t)); group->slowstart = 1; group->seq_wrap = 0; group->start_txseq = ntohs(header->seq); group->max_txseq = group->start_txseq; group->loss_history[group->start_txseq].found = 1; group->loss_history[group->start_txseq].t = rxtime; group->loss_history[group->start_txseq].size = packetlen; } return 1; }
/** * Read encryption related fields from an ANNOUNCE */ int read_announce_encryption(struct group_list_t *group, struct enc_info_he *encinfo, const unsigned char *packet, int packetlen) { int keyextype, sigtype, keytype, i; unsigned char *keys; keys = (unsigned char *)encinfo + sizeof(struct enc_info_he); // Sanity check the selected encryption parameters if (!cipher_supported(encinfo->keytype)) { glog1(group, "Keytype invalid or not supported here"); send_abort(group, "Keytype invalid or not supported here"); return 0; } if (!hash_supported(encinfo->hashtype)) { glog1(group, "Hashtype invalid or not supported here"); send_abort(group, "Hashtype invalid or not supported here"); return 0; } keyextype = (encinfo->keyextype_sigtype & 0xF0) >> 4; sigtype = encinfo->keyextype_sigtype & 0x0F; if (((sigtype != SIG_HMAC) && (sigtype != SIG_KEYEX) && (sigtype != SIG_AUTHENC)) || ((sigtype == SIG_AUTHENC) && (!is_auth_enc(encinfo->keytype)))) { glog1(group, "Invalid sigtype specified"); send_abort(group, "Invalid sigtype specified"); return 0; } if ((keyextype != KEYEX_RSA) && (keyextype != KEYEX_ECDH_RSA) && (keyextype != KEYEX_ECDH_ECDSA)) { glog1(group, "Invalid keyextype specified"); send_abort(group, "Invalid keyextype specified"); return 0; } group->keyextype = keyextype; group->keytype = encinfo->keytype; group->hashtype = encinfo->hashtype; group->sigtype = sigtype; group->client_auth = ((encinfo->flags & FLAG_CLIENT_AUTH) != 0); if (!verify_server_fingerprint(keys, ntohs(encinfo->keylen), group)) { glog1(group, "Failed to verify server key fingerprint"); send_abort(group, "Failed to verify server key fingerprint"); return 0; } if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { keytype = KEYBLOB_RSA; } else { keytype = KEYBLOB_EC; } // Load server key and select a matching client key if (keytype == KEYBLOB_RSA) { if (!import_RSA_key(&group->server_pubkey.rsa, keys, ntohs(encinfo->keylen))) { glog0(group, "Failed to load server public key"); send_abort(group, "Failed to load server public key"); return 0; } group->server_pubkeylen = RSA_keylen(group->server_pubkey.rsa); for (i = 0; i < key_count; i++) { if ((privkey_type[i] == KEYBLOB_RSA) && (group->server_pubkeylen == RSA_keylen(privkey[i].rsa))) { group->client_privkey = privkey[i]; group->client_privkeylen = RSA_keylen(privkey[i].rsa); break; } } } else { if (!import_EC_key(&group->server_pubkey.ec, keys, ntohs(encinfo->keylen), 0)) { glog0(group, "Failed to load server public key"); send_abort(group, "Failed to load server public key"); return 0; } group->server_pubkeylen = ECDSA_siglen(group->server_pubkey.ec); for (i = 0; i < key_count; i++) { if ((privkey_type[i] == KEYBLOB_EC) && (get_EC_curve(group->server_pubkey.ec) == get_EC_curve(privkey[i].ec))) { group->client_privkey = privkey[i]; group->client_privkeylen = ECDSA_siglen(privkey[i].ec); break; } } } if (!group->client_privkey.key) { glog1(group, "No client key compatible with server key"); send_abort(group, "No client key compatible with server key"); return 0; } if (has_proxy) { if (!proxy_pubkey.key) { glog1(group, "Response proxy set but haven't gotten key yet"); send_abort(group,"Response proxy set but haven't gotten key yet"); return 0; } if (!(((keytype == KEYBLOB_RSA) && (proxy_pubkeytype == KEYBLOB_RSA)) && (RSA_keylen(group->server_pubkey.rsa) == RSA_keylen(proxy_pubkey.rsa))) && !(((keytype == KEYBLOB_EC) && (proxy_pubkeytype==KEYBLOB_EC)) && (get_EC_curve(group->server_pubkey.ec) == get_EC_curve(proxy_pubkey.ec)))) { glog1(group, "Response proxy key not compatible with server key"); send_abort(group, "Response proxy key not compatible with server key"); return 0; } } if ((group->keyextype == KEYEX_ECDH_ECDSA) || (group->keyextype == KEYEX_ECDH_RSA)) { unsigned char *sigcopy; int siglen; unsigned char *dhblob = keys + ntohs(encinfo->keylen); unsigned char *sig = dhblob + ntohs(encinfo->dhlen); if (!import_EC_key(&group->server_dhkey.ec, dhblob, ntohs(encinfo->dhlen), 1)) { glog0(group, "Failed to load server public ECDH key"); send_abort(group, "Failed to load server public ECDH key"); return 0; } group->client_dhkey.ec = gen_EC_key(get_EC_curve(group->server_dhkey.ec), 1, NULL); if (!group->client_dhkey.key) { glog0(group, "Failed to generate client ECDH key"); send_abort(group, "Failed to generate client ECDH key"); return 0; } if (has_proxy) { // We already checked if the proxy key exists, so no need to repeat if (get_EC_curve(group->server_dhkey.ec) != get_EC_curve(proxy_dhkey.ec)) { glog1(group, "Response proxy ECDH key " "not compatible with server ECDH key"); send_abort(group, "Response proxy ECDH key " "not compatible with server ECDH key"); return 0; } } siglen = ntohs(encinfo->siglen); sigcopy = safe_calloc(siglen, 1); memcpy(sigcopy, sig, siglen); memset(sig, 0, siglen); if (keytype == KEYBLOB_RSA) { if (!verify_RSA_sig(group->server_pubkey.rsa, group->hashtype, packet, packetlen, sigcopy, siglen)) { glog1(group, "Signature verification failed"); send_abort(group, "Signature verification failed"); free(sigcopy); return 0; } } else { if (!verify_ECDSA_sig(group->server_pubkey.ec, group->hashtype, packet, packetlen, sigcopy, siglen)) { glog1(group, "Signature verification failed"); send_abort(group, "Signature verification failed"); free(sigcopy); return 0; } } free(sigcopy); } // Calculate keys if (!calculate_server_keys(group, encinfo)) { return 0; } return 1; }
/** * Sends a REGISTER message in response to an ANNOUNCE or on timeout when * waiting for a KEYINFO or REG_CONF. If the register timeout expired, abort. */ void send_register(struct group_list_t *group) { struct uftp_h *header; struct register_h *reg; unsigned char *buf, *keydata; struct timeval now, send_time; unsigned int len, meslen; union key_t key; gettimeofday(&now, NULL); if (cmptimestamp(now, group->expire_time) >= 0) { glog1(group, "Registration unconfirmed by server"); send_abort(group, "Registration unconfirmed"); return; } buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; reg = (struct register_h *)(buf + sizeof(struct uftp_h)); keydata = (unsigned char *)reg + sizeof(struct register_h); set_uftp_header(header, REGISTER, group); reg->func = REGISTER; if (group->keytype != KEY_NONE) { memcpy(reg->rand2, group->rand2, RAND_LEN); if (group->keyextype == KEYEX_RSA) { if (has_proxy) { key = proxy_pubkey; } else { key = group->server_pubkey; } if (!RSA_encrypt(key.rsa, group->premaster, group->premaster_len, keydata, &len)) { glog0(group, "Error encrypting premaster secret"); send_abort(group, "Error encrypting premaster secret"); free(buf); return; } } else { uint16_t keylen; if (!export_EC_key(group->client_dhkey.ec, keydata, &keylen)) { glog0(group, "Error exporting ECDH public key"); send_abort(group, "Error exporting ECDH public key"); free(buf); return; } len = keylen; } reg->keyinfo_len = htons(len); } else { len = 0; } gettimeofday(&now, NULL); if (cmptimestamp(now, group->last_server_rx_ts) <= 0) { send_time = group->last_server_ts; } else { send_time = add_timeval(group->last_server_ts, diff_timeval(now, group->last_server_rx_ts)); } reg->tstamp_sec = htonl((uint32_t)send_time.tv_sec); reg->tstamp_usec = htonl((uint32_t)send_time.tv_usec); reg->hlen = (sizeof(struct register_h) + len) / 4; meslen = sizeof(struct uftp_h) + (reg->hlen * 4); if (nb_sendto(listener, buf, meslen, 0, (struct sockaddr *)&(group->replyaddr), family_len(group->replyaddr)) == SOCKET_ERROR) { gsockerror(group, "Error sending REGISTER"); } else { glog2(group, "REGISTER sent"); } glog3(group, "send time: %d.%06d", send_time.tv_sec, send_time.tv_usec); set_timeout(group, 0); if (group->client_auth) { send_client_key(group); } free(buf); }
/** * Process an incoming KEYINFO message. * Expected in response to a REGISTER when encryption is enabled. */ void handle_keyinfo(struct group_list_t *group, unsigned char *message, unsigned meslen, uint32_t src_id) { struct keyinfo_h *keyinfo_hdr; struct destkey *keylist; int i, keyidx, len, destkeycnt, unauth_keytype, unauth_keylen, unauth_ivlen; unsigned explen, declen; uint8_t decgroupmaster[MASTER_LEN], *prf_buf, *iv; uint64_t ivctr; keyinfo_hdr = (struct keyinfo_h *)message; keylist = (struct destkey *)(message + (keyinfo_hdr->hlen * 4)); if ((meslen < (keyinfo_hdr->hlen * 4U)) || ((keyinfo_hdr->hlen * 4U) < sizeof(struct keyinfo_h))) { glog1(group, "Rejecting KEYINFO from server: invalid message size"); return; } destkeycnt = (meslen - (keyinfo_hdr->hlen * 4)) / sizeof(struct destkey); // This duplicates uid_in_list, but here it's addressed in a struct array for (i = 0, keyidx = -1; (i < destkeycnt) && (keyidx == -1); i++) { if (uid == keylist[i].dest_id) { keyidx = i; } } // Don't use a cipher in an authentication mode to decrypt the group master unauth_keytype = unauth_key(group->keytype); get_key_info(unauth_keytype, &unauth_keylen, &unauth_ivlen); if (keyidx != -1) { glog2(group, "Received KEYINFO"); if (group->phase == PHASE_MIDGROUP) { // We already got the KEYINFO, so no need to reprocess. // Just resend the KEYINFO_ACK and reset the timeout send_keyinfo_ack(group); set_timeout(group, 0); return; } iv = safe_calloc(unauth_ivlen, 1); ivctr = ntohl(keyinfo_hdr->iv_ctr_lo); ivctr |= (uint64_t)ntohl(keyinfo_hdr->iv_ctr_hi) << 32; build_iv(iv, group->salt, unauth_ivlen, uftp_htonll(ivctr), src_id); if (!decrypt_block(unauth_keytype, iv, group->key, NULL, 0, keylist[keyidx].groupmaster, MASTER_LEN, decgroupmaster, &declen) || (declen != MASTER_LEN - 1)) { glog1(group, "Decrypt failed for group master"); send_abort(group, "Decrypt failed for group master"); free(iv); return; } free(iv); group->groupmaster[0] = group->version; memcpy(&group->groupmaster[1], decgroupmaster, declen); explen = group->keylen + SALT_LEN + group->hmaclen; prf_buf = safe_calloc(explen + group->hmaclen, 1); PRF(group->hashtype, explen, group->groupmaster, sizeof(group->groupmaster), "key expansion", group->rand1, sizeof(group->rand1), prf_buf, &len); memcpy(group->grouphmackey, prf_buf, group->hmaclen); memcpy(group->groupkey, prf_buf + group->hmaclen, group->keylen); memcpy(group->groupsalt, prf_buf + group->hmaclen + group->keylen, SALT_LEN); free(prf_buf); group->phase = PHASE_MIDGROUP; send_keyinfo_ack(group); set_timeout(group, 0); if (group->restart) { read_restart_file(group); } } }
/** * Handles an incoming CLIENT_KEY message from a client. */ void handle_clientkey(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen, uint32_t src) { const struct client_key_h *clientkey; const unsigned char *keyblob, *verify; struct pr_destinfo_t *dest; int dupmsg; clientkey = (const struct client_key_h *)message; keyblob = (const unsigned char *)clientkey + sizeof(struct client_key_h); verify = keyblob + ntohs(clientkey->bloblen); if (group->destcount == MAXPROXYDEST) { glog1(group, "Rejecting CLIENT_KEY from %08X: " "max destinations exceeded", ntohl(src)); send_downstream_abort(group, src, "Max destinations exceeded", 0); return; } if ((meslen < (clientkey->hlen * 4U)) || ((clientkey->hlen * 4U) < sizeof(struct client_key_h) + ntohs(clientkey->bloblen) + ntohs(clientkey->siglen))) { glog1(group, "Rejecting CLIENT_KEY from %08X: invalid message size", ntohl(src)); send_downstream_abort(group, src, "Invalid message size", 0); return; } if ((((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) && (keyblob[0] != KEYBLOB_RSA)) || ((group->keyextype == KEYEX_ECDH_ECDSA) && (keyblob[0] != KEYBLOB_EC))) { glog1(group, "Rejecting CLIENT_KEY from %08X: invalid keyblob type", ntohl(src)); send_downstream_abort(group, src, "Invalid keyblob type", 0); return; } if (hostidx == -1) { hostidx = add_client(src, group); } dest = &group->destinfo[hostidx]; dupmsg = (dest->pubkey.key != 0); if (!dest->verified) { if (keyblob[0] == KEYBLOB_RSA) { if (!import_RSA_key(&dest->pubkey.rsa, keyblob, ntohs(clientkey->bloblen))) { glog1(group, "Failed to load client public key"); send_downstream_abort(group, src, "Failed to load client public key", 0); return; } dest->pubkeylen = RSA_keylen(dest->pubkey.rsa); } else { if (!import_EC_key(&dest->pubkey.ec, keyblob, ntohs(clientkey->bloblen), 0)) { glog1(group, "Failed to load client public key"); send_downstream_abort(group, src, "Failed to load client public key", 0); return; } dest->pubkeylen = ECDSA_siglen(dest->pubkey.ec); } if (!verify_fingerprint(client_fp, client_fp_count, keyblob, ntohs(clientkey->bloblen), group, src)) { glog1(group, "Failed to verify client key fingerprint"); send_downstream_abort(group, src, "Failed to verify client key fingerprint", 0); return; } dest->verified = 1; } memcpy(dest->verifydata, verify, ntohs(clientkey->siglen)); dest->verifylen = ntohs(clientkey->siglen); if (dest->registered) { if (!verify_client_key(group, hostidx)) { return; } dest->state = PR_CLIENT_REGISTERED; } glog2(group,"Received CLIENT_KEY%s from %s", dupmsg ? "+" : "", dest->name); if (dest->state == PR_CLIENT_REGISTERED) { // Pass in a dummy REGISTER message to check_pending, since // CLIENT_KEY is basically an extension of REGISTER. struct register_h reg; reg.func = REGISTER; check_pending(group, hostidx, (unsigned char *)®); } }
/** * Creates all directories in the given file's path, removing existing files. * Returns 1 on success, 0 on failure */ int create_path_to_file(struct group_list_t *group, const char *filename) { char *dir, *base; stat_struct statbuf; int rval; split_path(filename, &dir, &base); if (!dir) { glog1(group, "Invalid path element %s", filename); rval = 0; goto end; } #ifdef WINDOWS if ((base == NULL) || ((strlen(dir) == 2) && (dir[1] == ':'))) { #else if ((!strcmp(dir, ".")) || (!strcmp(dir, "/"))) { #endif // At top level directory, so stop recursion rval = 1; goto end; } if (lstat_func(dir, &statbuf) != -1) { if (!S_ISDIR(statbuf.st_mode)) { if (unlink(dir) == -1) { gsyserror(group, "Failed to delete path element %s", dir); rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { gsyserror(group, "Failed to create path element %s", dir); rval = 0; goto end; } } } else { // If the file's directory does not exist, recurse first to make sure // all parent directories exist if (!create_path_to_file(group, dir)) { rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { gsyserror(group, "Failed to create path element %s", dir); rval = 0; goto end; } } rval = 1; end: free(dir); free(base); return rval; } void new_loss_event(struct group_list_t *group, uint16_t txseq) { uint32_t seq_long; uint16_t count; int bytes, avgbytes, rate, grtt_usec; glog4(group, "Seq %d starts new loss event", txseq); // Found a new loss event if (txseq < group->max_txseq - MAXMISORDER) { glog5(group, "wrap check, i=%u, maxseq=%u", txseq, group->max_txseq); seq_long = ((group->seq_wrap - 1) << 16) | txseq; } else { seq_long = (group->seq_wrap << 16) | txseq; } if (group->slowstart) { group->slowstart = 0; // Initialize loss history count = group->max_txseq; bytes = 0; grtt_usec = (int)(group->grtt * 1000000); while ((count != group->start_txseq) && (diff_usec(group->loss_history[txseq].t, group->loss_history[count].t) < grtt_usec)) { bytes += group->loss_history[count--].size; } rate = (int)(bytes / group->grtt); glog4(group, "End slowstart, calculated rate = %d", rate); avgbytes= bytes / ((int16_t)(group->max_txseq - count)); group->loss_events[0].len = (int)(0 + pow( (rate * ((group->rtt != 0) ? group->rtt : group->grtt)) / (sqrt(1.5) * 8 * avgbytes), 2)); glog4(group, "Calculated prior event len = %d (rtt=%f, avgbytes=%d)", group->loss_events[0].len, group->rtt,avgbytes); } else { group->loss_events[0].len = seq_long - group->loss_events[0].start_seq; glog4(group, "Prior event length = %d (i=%u, start=%u)", group->loss_events[0].len, seq_long, group->loss_events[0].start_seq); } memmove(&group->loss_events[1], &group->loss_events[0], sizeof(struct loss_event_t) * 8); group->loss_events[0].start_seq = seq_long; group->loss_events[0].len = 0; group->loss_events[0].t = group->loss_history[txseq].t; }
/** * Puts the given message on the pending message list. If it doesn't match * any pending message and there are no open slots, first send what's pending. * If the pending list is full after adding the given message, then send. */ void check_pending(struct pr_group_list_t *group, int hostidx, const unsigned char *message) { const struct fileinfoack_h *fileinfoack; const struct status_h *status; const struct complete_h *complete; const uint8_t *func; struct pr_pending_info_t *pending; int match, pendidx, hlen; func = message; fileinfoack = (const struct fileinfoack_h *)message; status = (const struct status_h *)message; complete = (const struct complete_h *)message; glog3(group, "check_timeout: looking for pending %s", func_name(*func)); for (pendidx = 0; pendidx < MAX_PEND; pendidx++) { pending = &group->pending[pendidx]; if (group->pending[pendidx].msg == 0) { glog3(group, "check_timeout: found empty slot %d", pendidx); match = 1; break; } match = (*func == pending->msg); switch (*func) { case REGISTER: // REGISTER always matches itself break; case FILEINFO_ACK: match = match && (ntohs(fileinfoack->file_id) == pending->file_id); break; case STATUS: match = match && ((ntohs(status->file_id) == pending->file_id) && (ntohs(status->section) == pending->section)); break; case COMPLETE: match = match && ((ntohs(complete->file_id) == pending->file_id) && (complete->status == pending->comp_status)); break; default: glog1(group, "Tried to check pending on invalid type %s", func_name(*func)); return; } if (match) { break; } } if (!match) { send_all_pending(group); pendidx = 0; pending = &group->pending[pendidx]; } glog3(group, "check_timeout: found match at slot %d", pendidx); pending->msg = *func; if (group->destinfo[hostidx].pending != pendidx) { group->destinfo[hostidx].pending = pendidx; pending->count++; } switch (*func) { case REGISTER: hlen = sizeof(struct register_h); if (pending->count == 1) { gettimeofday(&pending->rx_tstamp, NULL); pending->tstamp = group->destinfo[hostidx].regtime; glog3(group, "send time = %d.%06d", pending->tstamp.tv_sec, pending->tstamp.tv_usec); glog3(group, "rx time = %d.%06d", pending->rx_tstamp.tv_sec, pending->rx_tstamp.tv_usec); } break; case FILEINFO_ACK: hlen = sizeof(struct fileinfoack_h); if (pending->count == 1) { pending->partial = 1; gettimeofday(&pending->rx_tstamp, NULL); pending->tstamp.tv_sec = ntohl(fileinfoack->tstamp_sec); pending->tstamp.tv_usec = ntohl(fileinfoack->tstamp_usec); glog3(group, "send time = %d.%06d", pending->tstamp.tv_sec, pending->tstamp.tv_usec); glog3(group, "rx time = %d.%06d", pending->rx_tstamp.tv_sec, pending->rx_tstamp.tv_usec); } pending->file_id = ntohs(fileinfoack->file_id); pending->partial = pending->partial && ((fileinfoack->flags & FLAG_PARTIAL) != 0); break; case STATUS: hlen = sizeof(struct status_h); pending->file_id = ntohs(status->file_id); pending->section = ntohs(status->section); if (!pending->naklist) { pending->naklist = safe_calloc(group->blocksize, 1); } add_naks_to_pending(group, pendidx, message); break; case COMPLETE: hlen = sizeof(struct complete_h); pending->file_id = ntohs(complete->file_id); pending->comp_status = complete->status; break; } if ((*func != STATUS) && (pending->count == max_msg_dest(group, *func, hlen))) { send_pending(group, pendidx); } else { int total_pending, i; glog3(group, "check_timeout: getting pending count for %s", func_name(*func)); for (total_pending = 0, i = 0; i < MAX_PEND; i++) { glog3(group, "check_timeout: adding %d pending for %d", group->pending[i].count, i); total_pending += group->pending[i].count; } if (total_pending == 1) { set_timeout(group, 1, 0); } } }
/** * Forward a message unmodified to the next hop, resigning if necessary. */ void forward_message(struct pr_group_list_t *group, const union sockaddr_u *src, unsigned char *packet, int packetlen) { struct uftp_h *header; struct encrypted_h *encrypted; struct announce_h *announce; struct enc_info_he *encinfo; union sockaddr_u dest; unsigned int meslen, siglen; int hostidx, rval, iplen, resign; char destname[INET6_ADDRSTRLEN], destport[PORTNAME_LEN]; uint8_t *sig, *sigcopy; union key_t key; header = (struct uftp_h *)packet; meslen = (unsigned int)packetlen; memset(&dest, 0, sizeof(dest)); if (!memcmp(src, &group->up_addr, sizeof(*src))) { if (proxy_type == RESPONSE_PROXY) { // Response proxy, no downstream forwarding set_timeout(group, 0, 0); return; } else if (proxy_type == SERVER_PROXY) { dest = down_addr; } else { if (header->func == ANNOUNCE) { dest = group->publicmcast; } else { dest = group->privatemcast; } key = group->server_pubkey; } } else { dest = group->up_addr; if (proxy_type != SERVER_PROXY) { hostidx = find_client(group, header->src_id); if (hostidx == -1) { glog1(group, "Couldn't find receiver in list"); return; } key = group->destinfo[hostidx].pubkey; } } // If we're using KEYEX signatures, or sending an ANNOUNCE with ECDH, // verify the signature and resign resign = 0; if ((proxy_type != SERVER_PROXY) && (header->func == ENCRYPTED) && (group->sigtype == SIG_KEYEX)) { encrypted = (struct encrypted_h *)(packet + sizeof(struct uftp_h)); sig = (uint8_t *)encrypted + sizeof(struct encrypted_h); siglen = ntohs(encrypted->sig_len); resign = 1; } else if ((proxy_type != SERVER_PROXY) && (header->func == ANNOUNCE) && ((group->keyextype == KEYEX_ECDH_RSA) || (group->keyextype == KEYEX_ECDH_ECDSA))) { announce = (struct announce_h *)(packet + sizeof(struct uftp_h)); iplen = ((announce->flags & FLAG_IPV6) != 0) ? 16 : 4; encinfo = (struct enc_info_he *) ((uint8_t *)announce + sizeof(struct announce_h) + iplen + iplen); sig = (uint8_t *)encinfo + sizeof(struct enc_info_he) + ntohs(encinfo->keylen) + ntohs(encinfo->dhlen); siglen = ntohs(encinfo->siglen); resign = 1; } if (resign) { sigcopy = safe_calloc(siglen, 1); memcpy(sigcopy, sig, siglen); memset(sig, 0, siglen); if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (header->func == ENCRYPTED) { if (!verify_RSA_sig(key.rsa, group->hashtype, packet, meslen, sigcopy, siglen)) { glog1(group, "Signature verification failed"); free(sigcopy); return; } } if (!create_RSA_sig(group->proxy_privkey.rsa, group->hashtype, packet, meslen, sigcopy, &siglen)) { glog0(group, "Signature creation failed"); free(sigcopy); return; } } else { if (header->func == ENCRYPTED) { if (!verify_ECDSA_sig(key.ec, group->hashtype, packet, meslen, sigcopy, siglen)) { glog1(group, "Signature verification failed"); free(sigcopy); return; } } if (!create_ECDSA_sig(group->proxy_privkey.ec, group->hashtype, packet, meslen, sigcopy, &siglen)) { glog0(group, "Signature creation failed"); free(sigcopy); return; } } memcpy(sig, sigcopy, siglen); free(sigcopy); } if (nb_sendto(listener, packet, meslen, 0, (struct sockaddr *)&dest, family_len(dest)) == SOCKET_ERROR) { gsockerror(group, "Error forwarding message"); if ((rval = getnameinfo((struct sockaddr *)&dest, family_len(dest), destname, sizeof(destname), destport, sizeof(destport), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { glog1(group, "getnameinfo failed: %s", gai_strerror(rval)); } glog2(group, "Dest: %s:%s", destname, destport); } set_timeout(group, 0, 0); }