/** * Sends a KEYINFO_ACK in response to a KEYINFO */ void send_keyinfo_ack(struct group_list_t *group) { unsigned char *buf, *encrypted; struct uftp_h *header; struct keyinfoack_h *keyinfo_ack; unsigned char *verifydata, *verify_hash, *verify_val; unsigned int payloadlen, hashlen; int verifylen, enclen, len; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; keyinfo_ack = (struct keyinfoack_h *)(buf + sizeof(struct uftp_h)); set_uftp_header(header, KEYINFO_ACK, group); keyinfo_ack->func = KEYINFO_ACK; keyinfo_ack->hlen = sizeof(struct keyinfoack_h) / 4; verifydata = build_verify_data(group, &verifylen); if (!verifydata) { glog0(group, "Error getting verify data"); send_abort(group, "Error getting verify data"); free(buf); return; } verify_hash = safe_calloc(group->hmaclen, 1); verify_val = 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_val, &len); memcpy(keyinfo_ack->verify_data, verify_val, VERIFY_LEN); free(verifydata); free(verify_hash); free(verify_val); payloadlen = sizeof(struct keyinfoack_h); 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 KEYINFO_ACK"); free(buf); return; } payloadlen = enclen + sizeof(struct uftp_h); if (nb_sendto(listener, encrypted, payloadlen, 0, (struct sockaddr *)&(group->replyaddr), family_len(group->replyaddr)) == SOCKET_ERROR) { gsockerror(group, "Error sending KEYINFO_ACK"); } else { glog2(group, "KEYINFO_ACK sent"); } free(encrypted); free(buf); }
/** * Moves a file from the temp to the destination directory */ void move_file_individual(struct group_list_t *group, const char *local_temp, const char *local_dest, const char *filename) { char temppath[MAXPATHNAME], destpath[MAXPATHNAME]; stat_struct temp_stat, dest_stat; int len, found_dir; len = snprintf(temppath, sizeof(temppath), "%s%c%s", local_temp, PATH_SEP, filename); if ((len >= sizeof(temppath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", local_temp, PATH_SEP, filename); return; } len = snprintf(destpath, sizeof(destpath), "%s%c%s", local_dest, PATH_SEP, filename); if ((len >= sizeof(destpath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", local_dest, PATH_SEP, filename); return; } if (lstat_func(temppath, &temp_stat) == -1) { gsyserror(group, "Error getting file status for %s", temppath); return; } if (S_ISDIR(temp_stat.st_mode)) { found_dir = 0; if (lstat_func(destpath, &dest_stat) != -1) { if (!S_ISDIR(dest_stat.st_mode)) { clear_path(destpath, group); } else { found_dir = 1; } } if (!found_dir) { if (mkdir(destpath, 0755) == -1) { gsyserror(group, "Failed to create directory %s", destpath); return; } } move_files_individual(group, temppath, destpath); } else { clear_path(destpath, group); #ifdef WINDOWS if (!MoveFile(temppath, destpath)) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf),NULL); glog0(group, "error (%d): %s", GetLastError(), errbuf); } #else if (rename(temppath, destpath) == -1) { gsyserror(group, "Couldn't move file"); } #endif run_postreceive(group, destpath); } }
/** * Sets the fields in a EXT_ENC_INFO extension for transmission. * Returns the number of bytes set, or 0 on error. */ int set_enc_info(const struct finfo_t *finfo, struct enc_info_he *encinfo) { unsigned char *keyblob; uint16_t bloblen; int extlen; keyblob = ((uint8_t *)encinfo + sizeof(struct enc_info_he)); encinfo->exttype = EXT_ENC_INFO; encinfo->keyextype_sigtype = (keyextype & 0x0F) << 4; encinfo->keyextype_sigtype |= (sigtype & 0x0F); encinfo->keytype = keytype; encinfo->hashtype = hashtype; if (client_auth) { encinfo->flags |= FLAG_CLIENT_AUTH; } memcpy(encinfo->rand1, rand1, sizeof(rand1)); if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { if (!export_RSA_key(privkey.rsa, keyblob, &bloblen)) { glog0(finfo, "Error exporting server public key"); return 0; } } else { if (!export_EC_key(privkey.ec, keyblob, &bloblen)) { glog0(finfo, "Error exporting server public key"); return 0; } } encinfo->keylen = htons(bloblen); if ((keyextype == KEYEX_ECDH_RSA) || (keyextype == KEYEX_ECDH_ECDSA)) { uint16_t dhlen; uint8_t *dhblob = ((uint8_t *)encinfo + sizeof(struct enc_info_he) + ntohs(encinfo->keylen)); if (!export_EC_key(dhkey.ec, dhblob, &dhlen)) { glog0(finfo, "Error exporting server ECDH public key"); return 0; } encinfo->dhlen = htons(dhlen); if (keyextype == KEYEX_ECDH_RSA) { encinfo->siglen = htons(RSA_keylen(privkey.rsa)); } else { encinfo->siglen = htons(ECDSA_siglen(privkey.ec)); } } else { encinfo->dhlen = 0; encinfo->siglen = 0; } extlen = sizeof(struct enc_info_he) + ntohs(encinfo->keylen) + ntohs(encinfo->dhlen) + ntohs(encinfo->siglen); encinfo->extlen = extlen / 4; return extlen; }
/** * Save the state of a failed transfer so it can restarted later. */ void write_restart_file(struct group_list_t *group) { struct file_t *fileinfo; struct client_restart_t restart; char restart_name[MAXPATHNAME]; int fd; // Don't bother if we're not using a temp directory. if (!strcmp(tempdir, "")) { return; } glog2(group, "Writing restart file"); memset(&restart, 0, sizeof(restart)); fileinfo = &group->fileinfo; if (group->phase != PHASE_MIDGROUP) { restart.blocks = fileinfo->blocks; restart.sections = fileinfo->sections; restart.size = fileinfo->size; strncpy(restart.name, fileinfo->name, sizeof(restart.name)); restart.name[sizeof(restart.name)-1] = '\x0'; } snprintf(restart_name, sizeof(restart_name), "%s%c_group_%08X_restart", tempdir, PATH_SEP, group->group_id); if ((fd = open(restart_name, OPENWRITE | O_CREAT | O_TRUNC, 0644)) == -1) { gsyserror(group, "Failed to create restart file"); return; } if (file_write(fd, &restart, sizeof(restart)) == -1) { glog0(group, "Failed to write header for restart file"); goto errexit; } if (fileinfo->blocks && fileinfo->naklist) { if (file_write(fd, fileinfo->naklist, fileinfo->blocks) == -1) { glog0(group, "Failed to write NAK list for restart file"); goto errexit; } } if (fileinfo->sections && fileinfo->section_done) { if (file_write(fd, fileinfo->section_done, fileinfo->sections) == -1) { glog0(group, "Failed to write section_done list for restart file"); goto errexit; } } close(fd); return; errexit: close(fd); unlink(restart_name); }
/** * Flushes the cache to disk * Returns 1 on success, 0 on failure */ int flush_disk_cache(struct group_list_t *group) { f_offset_t offset, seek_rval; int wrote_len; uint32_t i; if (group->fileinfo.cache_len == 0) return 1; offset = (f_offset_t) group->fileinfo.cache_start * group->blocksize; if ((seek_rval = lseek_func(group->fileinfo.fd, offset - group->fileinfo.curr_offset, SEEK_CUR)) == -1) { gsyserror(group, "lseek failed for file"); } if (seek_rval != offset) { glog2(group, "offset is %s", printll(seek_rval)); glog2(group, " should be %s", printll(offset)); if ((seek_rval = lseek_func(group->fileinfo.fd, offset, SEEK_SET)) == -1) { gsyserror(group, "lseek failed for file"); return 0; } } if ((wrote_len = write(group->fileinfo.fd, group->fileinfo.cache, group->fileinfo.cache_len)) == -1) { gsyserror(group, "Write failed for blocks %d - %d", group->fileinfo.cache_start, group->fileinfo.cache_end); return 0; } else { group->fileinfo.curr_offset = offset + wrote_len; if (wrote_len != group->fileinfo.cache_len) { glog0(group, "Write failed for blocks %d - %d, only wrote %d bytes", group->fileinfo.cache_start, group->fileinfo.cache_end); return 0; } else { glog4(group, "Wrote blocks %d - %d to disk from cache", group->fileinfo.cache_start, group->fileinfo.cache_end); for (i = group->fileinfo.cache_start; i <= group->fileinfo.cache_end; i++) { int status_idx = i - group->fileinfo.cache_start; if (group->fileinfo.cache_status[status_idx]) { group->fileinfo.naklist[i] = 0; } } group->fileinfo.cache_start = group->fileinfo.cache_end + 1; while ((group->fileinfo.cache_start < group->fileinfo.blocks) && (!group->fileinfo.naklist[group->fileinfo.cache_start])) { group->fileinfo.cache_start++; } group->fileinfo.cache_end = group->fileinfo.cache_start; group->fileinfo.cache_len = 0; memset(group->fileinfo.cache, 0, cache_len); memset(group->fileinfo.cache_status,0,cache_len / group->blocksize); return 1; } } }
/** * Sends back a CC_ACK message for congestion control feedback */ void send_cc_ack(struct group_list_t *group) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct cc_ack_h *cc_ack; struct tfmcc_ack_info_he *tfmcc; int payloadlen, enclen; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; cc_ack = (struct cc_ack_h *)(buf + sizeof(struct uftp_h)); tfmcc = (struct tfmcc_ack_info_he *)((unsigned char *)cc_ack + sizeof(struct cc_ack_h)); set_uftp_header(header, CC_ACK, group); cc_ack->func = CC_ACK; cc_ack->hlen = (sizeof(struct cc_ack_h) + sizeof(struct tfmcc_ack_info_he)) / 4; set_tfmcc_ack_info(group, tfmcc); payloadlen = cc_ack->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 CC_ACK"); 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 CC_ACK"); } else { glog2(group, "CC_ACK sent"); } set_timeout(group, 0); group->cc_time.tv_sec = 0; group->cc_time.tv_usec = 0; free(buf); }
/** * Sends an ABORT message to a server */ void send_abort(struct group_list_t *group, const char *message) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct abort_h *abort_hdr; int payloadlen, enclen; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; abort_hdr = (struct abort_h *)(buf + sizeof(struct uftp_h)); set_uftp_header(header, ABORT, group); abort_hdr->func = ABORT; abort_hdr->hlen = sizeof(struct abort_h) / 4; abort_hdr->host = 0; strncpy(abort_hdr->message, message, sizeof(abort_hdr->message) - 1); payloadlen = sizeof(struct abort_h); 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 ABORT"); 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 ABORT"); } flush_disk_cache(group); file_cleanup(group, 1); free(buf); free(encrypted); }
/** * Send a KEYINFO message. Sent during the Announce phase for a group * with encryption enabled. * Returns 1 on success, 0 on fail. */ int send_keyinfo(const struct finfo_t *finfo, int attempt) { unsigned char *buf, *iv; struct uftp_h *header; struct keyinfo_h *keyinfo; struct destkey *keylist; unsigned int hsize, payloadlen, len; int maxdest, packetcnt, dests, iv_init, i; int unauth_keytype, unauth_keylen, unauth_ivlen; // Don't use a cipher in an authentication mode to encrypt the group master unauth_keytype = unauth_key(keytype); get_key_info(unauth_keytype, &unauth_keylen, &unauth_ivlen); buf = safe_calloc(MAXMTU, 1); iv = safe_calloc(unauth_ivlen, 1); header = (struct uftp_h *)buf; keyinfo = (struct keyinfo_h *)(buf + sizeof(struct uftp_h)); keylist = (struct destkey *)((char *)keyinfo + sizeof(struct keyinfo_h)); set_uftp_header(header, KEYINFO, finfo->group_id, finfo->group_inst, grtt, destcount); keyinfo->func = KEYINFO; keyinfo->hlen = sizeof(struct keyinfo_h) / 4; keylist = (struct destkey *)((uint8_t *)keyinfo + (keyinfo->hlen * 4)); iv_init = 0; hsize = sizeof(struct keyinfo_h); maxdest = blocksize / sizeof(struct destkey); packetcnt = 1; for (i = 0, dests = 0; i < destcount; i++) { if (destlist[i].status == DEST_REGISTERED) { if (!iv_init) { ivctr++; keyinfo->iv_ctr_hi =htonl((ivctr & 0xFFFFFFFF00000000LL) >> 32); keyinfo->iv_ctr_lo = htonl(ivctr & 0x00000000FFFFFFFFLL); iv_init = 1; } keylist[dests].dest_id = destlist[i].id; build_iv(iv, destlist[i].encinfo->salt, unauth_ivlen, uftp_htonll(ivctr), header->src_id); if (!encrypt_block(unauth_keytype, iv,destlist[i].encinfo->key, NULL,0, &groupmaster[1], sizeof(groupmaster) - 1, keylist[dests].groupmaster, &len)) { glog0(finfo, "Error encrypting KEYINFO for %s", destlist[i].name); free(buf); free(iv); return 0; } dests++; } if ((dests >= maxdest) || ((i == destcount - 1) && (dests > 0))) { header->seq = htons(send_seq++); payloadlen = hsize + (dests * sizeof(struct destkey)); glog2(finfo, "Sending KEYINFO %d.%d", attempt, packetcnt); if (nb_sendto(sock, buf, payloadlen + sizeof(struct uftp_h), 0, (struct sockaddr *)&receive_dest, family_len(receive_dest)) == SOCKET_ERROR) { gsockerror(finfo, "Error sending KEYINFO"); sleep(1); free(buf); free(iv); return 0; } if (packet_wait) usleep(packet_wait); memset(keylist, 0, maxdest * sizeof(struct destkey)); iv_init = 0; dests = 0; packetcnt++; } }
/** * Send the ANNOUNCE message * For open group membership, just send one. For closed group membership, * list as many destinations as will fit and send multiple packets so that * each receiver is listed. * Returns 1 on success, 0 on fail. */ int send_announce(const struct finfo_t *finfo, int attempt, int open) { int packetlen, rval, iplen, extlen; unsigned char *buf; struct uftp_h *header; struct announce_h *announce; unsigned char *publicaddr, *privateaddr; struct enc_info_he *encinfo; struct timeval tv; uint32_t *idlist; buf = safe_calloc(MAXMTU, 1); if (listen_dest.ss.ss_family == AF_INET6) { iplen = sizeof(struct in6_addr); } else { iplen = sizeof(struct in_addr); } header = (struct uftp_h *)buf; announce = (struct announce_h *)(buf + sizeof(struct uftp_h)); publicaddr = (unsigned char *)announce + sizeof(struct announce_h); privateaddr = publicaddr + iplen; encinfo = (struct enc_info_he *)(privateaddr + iplen); set_uftp_header(header, ANNOUNCE, finfo->group_id, finfo->group_inst, grtt, destcount); announce->func = ANNOUNCE; if (sync_mode) { announce->flags |= FLAG_SYNC_MODE; if (sync_preview) { announce->flags |= FLAG_SYNC_PREVIEW; } } announce->robust = robust; announce->cc_type = cc_type; announce->blocksize = htons(blocksize); gettimeofday(&tv, NULL); announce->tstamp_sec = htonl(tv.tv_sec); announce->tstamp_usec = htonl(tv.tv_usec); if (!is_multicast(&listen_dest, 0)) { memset(publicaddr, 0, iplen); memset(privateaddr, 0, iplen); } else if (listen_dest.ss.ss_family == AF_INET6) { memcpy(publicaddr, &listen_dest.sin6.sin6_addr.s6_addr, iplen); memcpy(privateaddr, &receive_dest.sin6.sin6_addr.s6_addr, iplen); } else { memcpy(publicaddr, &listen_dest.sin.sin_addr.s_addr, iplen); memcpy(privateaddr, &receive_dest.sin.sin_addr.s_addr, iplen); } if (listen_dest.ss.ss_family == AF_INET6) { announce->flags |= FLAG_IPV6; } if (keytype != KEY_NONE) { extlen = set_enc_info(finfo, encinfo); if (extlen == 0) { glog0(finfo, "Error setting up EXT_ENC_INFO"); free(buf); return 0; } announce->hlen = (sizeof(struct announce_h) + iplen + iplen + extlen) / 4; } else { announce->hlen = (sizeof(struct announce_h) + iplen + iplen) / 4; } idlist = (uint32_t *)((uint8_t *)announce + (announce->hlen * 4)); if (open) { header->seq = htons(send_seq++); packetlen = sizeof(struct uftp_h) + (announce->hlen * 4); if (!sign_announce(finfo, buf, packetlen)) { glog0(finfo, "Error signing ANNOUNCE"); free(buf); return 0; } glog2(finfo, "Sending ANNOUNCE %d", attempt); if (nb_sendto(sock, buf, packetlen, 0, (struct sockaddr *)&listen_dest, family_len(listen_dest)) == SOCKET_ERROR) { gsockerror(finfo, "Error sending ANNOUNCE"); // So we don't spin our wheels... sleep(1); free(buf); return 0; } free(buf); return 1; } else { rval = send_multiple(finfo, buf, ANNOUNCE, attempt, idlist, DEST_MUTE, 0, &listen_dest, 0); free(buf); return rval; } }
/** * 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; }
/** * 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); }
/** * 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); }
/** * 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); }
/** * Sends a CLIENT_KEY message if the server requested it. * Always sent right after a REGISTER. */ void send_client_key(struct group_list_t *group) { struct uftp_h *header; struct client_key_h *client_key; unsigned char *buf, *keyblob, *verify; uint8_t *verifydata; unsigned int siglen, meslen; int verifylen; uint16_t bloblen; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; client_key = (struct client_key_h *)(buf + sizeof(struct uftp_h)); keyblob = (unsigned char *)client_key + sizeof(struct client_key_h); verifydata = build_verify_data(group, &verifylen); if (!verifydata) { glog0(group, "Error getting verify data"); send_abort(group, "Error getting verify data"); goto end; } set_uftp_header(header, CLIENT_KEY, group); client_key->func = CLIENT_KEY; if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (!export_RSA_key(group->client_privkey.rsa, keyblob, &bloblen)) { glog0(group, "Error exporting public key"); send_abort(group, "Error exporting public key"); goto end; } verify = keyblob + bloblen; if (!create_RSA_sig(group->client_privkey.rsa, group->hashtype, verifydata, verifylen, verify, &siglen)) { glog0(group, "Error signing verify data"); send_abort(group, "Error signing verify data"); goto end; } } else { if (!export_EC_key(group->client_privkey.ec, keyblob, &bloblen)) { glog0(group, "Error exporting public key"); send_abort(group, "Error exporting public key"); goto end; } verify = keyblob + bloblen; if (!create_ECDSA_sig(group->client_privkey.ec, group->hashtype, verifydata, verifylen, verify, &siglen)) { glog0(group, "Error signing verify data"); send_abort(group, "Error signing verify data"); goto end; } } client_key->bloblen = htons(bloblen); client_key->siglen = htons(siglen); client_key->hlen = (sizeof(struct client_key_h) + bloblen + siglen) / 4; meslen = sizeof(struct uftp_h) + (client_key->hlen * 4); if (nb_sendto(listener, buf, meslen, 0, (struct sockaddr *)&(group->replyaddr), family_len(group->replyaddr)) == SOCKET_ERROR) { gsockerror(group, "Error sending CLIENT_KEY"); } else { glog2(group, "CLIENT_KEY sent"); } end: free(verifydata); free(buf); }
/** * For the current file in a group, move the existing file to * the appropriate backup directory, if it exists. * In the event of a failure, delete the original file */ void move_to_backup(struct group_list_t *group) { stat_struct statbuf; char backup_file[MAXBACKUPPATHNAME], *trim_name; int len; if (lstat_func(group->fileinfo.filepath, &statbuf) == -1) { return; } if (backupcnt == 0) { clear_path(group->fileinfo.filepath, group); return; } #ifdef WINDOWS if ((group->fileinfo.filepath[1] == ':') && (group->fileinfo.filepath[2] == '\\')) { trim_name = &group->fileinfo.filepath[3]; } else { trim_name = group->fileinfo.filepath; } #else trim_name = group->fileinfo.filepath; #endif len = snprintf(backup_file, sizeof(backup_file), "%s%c%s%c%s%c%s", backupdir[group->fileinfo.destdiridx], PATH_SEP, group->start_date, PATH_SEP, group->start_time, PATH_SEP, trim_name); if (len >= sizeof(backup_file)) { glog0(group, "Max pathname length exceeded for backup file, deleting", group->fileinfo.filepath); clear_path(group->fileinfo.filepath, group); return; } clear_path(backup_file, group); if (!create_path_to_file(group, backup_file)) { glog0(group, "Error creating path to backup file"); clear_path(group->fileinfo.filepath, group); } #ifdef WINDOWS if (!MoveFile(group->fileinfo.filepath, backup_file)) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); glog0(group, "Couldn't rename from %s to %s, deleting: (%d): %s", group->fileinfo.filepath, backup_file, GetLastError(), errbuf); clear_path(group->fileinfo.filepath, group); } else { glog2(group, "Backed up existing file to %s", backup_file); } #else if (rename(group->fileinfo.filepath, backup_file) == -1) { gsyserror(group, "Couldn't rename from %s to %s, deleting", group->fileinfo.filepath, backup_file); clear_path(group->fileinfo.filepath, group); } else { glog2(group, "Backed up existing file to %s", backup_file); } #endif }
/** * Removes a full path from disk */ void clear_path(const char *path, struct group_list_t *group) { stat_struct statbuf; char filename[MAXPATHNAME]; int len; if (lstat_func(path, &statbuf) == -1) { if (errno != ENOENT) { gsyserror(group, "Error getting file status for %s", path); } return; } if (!S_ISDIR(statbuf.st_mode)) { unlink(path); } else { #ifdef WINDOWS intptr_t ffhandle; struct _finddatai64_t finfo; char dirglob[MAXPATHNAME]; snprintf(dirglob, sizeof(dirglob), "%s%c*", path, PATH_SEP, group->group_id, PATH_SEP); if ((ffhandle = _findfirsti64(dirglob, &finfo)) == -1) { gsyserror(group, "Failed to open directory %s", path); return; } do { len = snprintf(filename, sizeof(filename), "%s%c%s", path, PATH_SEP, finfo.name); if ((len >= sizeof(filename)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", filename, PATH_SEP, finfo.name); continue; } if (strcmp(finfo.name, ".") && strcmp(finfo.name, "..")) { clear_path(filename, group); } } while (_findnexti64(ffhandle, &finfo) == 0); _findclose(ffhandle); #else DIR *dir; struct dirent *de; if ((dir = opendir(path)) == NULL) { gsyserror(group, "Failed to open directory %s", path); return; } // errno needs to be set to 0 before calling readdir, otherwise // we'll report a false error when we exhaust the directory while ((errno = 0, de = readdir(dir)) != NULL) { len = snprintf(filename, sizeof(filename), "%s%c%s", path, PATH_SEP, de->d_name); if ((len >= sizeof(filename)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", path, PATH_SEP, de->d_name); continue; } if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { clear_path(filename, group); } } if (errno && (errno != ENOENT)) { gsyserror(group, "Failed to read directory %s", path); } closedir(dir); #endif if (rmdir(path) == -1) { gsyserror(group, "Failed remove directory %s", path); } } }
/** * Run the postreceive script on list of received files */ void run_postreceive_multi(struct group_list_t *group, char *const *files, int count) { char **params; char gid_str[10]; char gid_param[] = "-I"; int i; if (!strcmp(postreceive, "")) { return; } params = safe_calloc(count + 4, sizeof(char *)); snprintf(gid_str, sizeof(gid_str), "%08X", group->group_id); params[0] = postreceive; params[1] = gid_param; params[2] = gid_str; for (i = 0; i < count; i++) { params[i+3] = files[i]; } params[count+4-1] = NULL; if (log_level >= 2) { cglog2(group, "Running postreceive: %s", postreceive); for (i = 1; i < count + 3; i++) { sclog2(" %s", params[i]); } slog2(""); } #ifdef WINDOWS { char cmdline[0x8000]; // Windows max command line length char cmdexe[MAXPATHNAME]; int too_long, rval, is_cmd; strcpy(cmdline, ""); if ((!strncmp(&postreceive[strlen(postreceive)-4], ".cmd", 4)) || (!strncmp(&postreceive[strlen(postreceive)-4], ".bat", 4))) { is_cmd = 1; if (!GetEnvironmentVariable("SystemRoot", cmdexe, sizeof(cmdexe))) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); glog0(group, "Error getting sysroot: (%d) %s", GetLastError(), errbuf); free(params); return; } strcat(cmdexe, "\\system32\\cmd.exe"); strcat(cmdline, "/c \""); } else { is_cmd = 0; } for (too_long = 0, i = 0; i < count + 3; i++) { int size = 0x8000 - strlen(cmdline); if (size <= (int)strlen(params[i]) + 4) { too_long = 1; break; } // Quote everything except -I {group_id} if (i == 1 || i == 2) { strcat(cmdline, params[i]); strcat(cmdline," "); } else { strcat(cmdline, "\""); strcat(cmdline, params[i]); strcat(cmdline,"\" "); } } if (is_cmd) { strcat(cmdline, "\""); } if (!too_long) { STARTUPINFO startup_info; PROCESS_INFORMATION proc_info; GetStartupInfo(&startup_info); rval = CreateProcess(is_cmd ? cmdexe : postreceive, cmdline, NULL, NULL, 0, CREATE_NO_WINDOW, NULL, NULL, &startup_info, &proc_info); if (!rval) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); glog0(group, "Error running script: (%d) %s", GetLastError(), errbuf); } } } #else { pid_t pid; if ((pid = fork()) == -1) { gsyserror(group, "fork failed"); } else if (pid == 0) { close(listener); close(1); close(2); execv(postreceive, params); gsyserror(group, "exec failed"); exit(1); } } #endif free(params); }
/** * Move all files from temp to destination directory if at end of group */ void move_files(struct group_list_t *group) { char temppath[MAXPATHNAME], destpath[MAXPATHNAME]; char *filelist[10000]; // TODO: no magic number int len, filecount, i; if (!strcmp(tempdir, "") || (group->file_id != 0)) { return; } if (move_individual) { len = snprintf(temppath, sizeof(temppath), "%s%c_group_%08X", tempdir, PATH_SEP, group->group_id); if ((len >= sizeof(temppath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c_group_%08X", tempdir, PATH_SEP, group->group_id); } else { move_files_individual(group, temppath, destdir[0]); } return; } { #ifdef WINDOWS intptr_t ffhandle; struct _finddatai64_t finfo; char dirglob[MAXPATHNAME]; snprintf(dirglob, sizeof(dirglob), "%s%c_group_%08X%c*", tempdir, PATH_SEP, group->group_id, PATH_SEP); if ((ffhandle = _findfirsti64(dirglob, &finfo)) == -1) { gsyserror(group, "Failed to open directory %s", dirglob); return; } filecount = 0; do { len = snprintf(temppath, sizeof(temppath), "%s%c_group_%08X%c%s", tempdir, PATH_SEP, group->group_id, PATH_SEP, finfo.name); if ((len >= sizeof(temppath)) || (len == -1)) { glog0(group,"Max pathname length exceeded: %s%c_group_%08X%c%s", tempdir, PATH_SEP, group->group_id, PATH_SEP, finfo.name); continue; } len = snprintf(destpath, sizeof(destpath), "%s%c%s", destdir[0], PATH_SEP, finfo.name); if ((len >= sizeof(destpath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", destdir[0], PATH_SEP, finfo.name); continue; } // do the move if (strcmp(finfo.name, ".") && strcmp(finfo.name, "..")) { clear_path(destpath, group); if (!MoveFile(temppath, destpath)) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); glog0(group, "error (%d): %s", GetLastError(), errbuf); } filelist[filecount] = strdup(destpath); if (filelist[filecount] == NULL) { gsyserror(group, "strdup failed!"); exit(ERR_ALLOC); } filecount++; } } while (_findnexti64(ffhandle, &finfo) == 0); _findclose(ffhandle); #else DIR *dir; struct dirent *de; char dirname[MAXPATHNAME]; snprintf(dirname, sizeof(dirname), "%s%c_group_%08X", tempdir, PATH_SEP, group->group_id); if ((dir = opendir(dirname)) == NULL) { gsyserror(group, "Failed to open directory %s", dirname); return; } filecount = 0; // errno needs to be set to 0 before calling readdir, otherwise // we'll report a false error when we exhaust the directory while ((errno = 0, de = readdir(dir)) != NULL) { len = snprintf(temppath, sizeof(temppath), "%s%c%s", dirname, PATH_SEP, de->d_name); if ((len >= sizeof(temppath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", dirname, PATH_SEP, de->d_name); continue; } len = snprintf(destpath, sizeof(destpath), "%s%c%s", destdir[0], PATH_SEP, de->d_name); if ((len >= sizeof(destpath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", destdir[0], PATH_SEP, de->d_name); continue; } if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { clear_path(destpath, group); if (rename(temppath, destpath) == -1) { gsyserror(group, "Couldn't move file"); } filelist[filecount] = strdup(destpath); if (filelist[filecount] == NULL) { gsyserror(group, "strdup failed!"); exit(ERR_ALLOC); } filecount++; } } if (errno && (errno != ENOENT)) { gsyserror(group, "Failed to read directory %s", dirname); } closedir(dir); #endif } run_postreceive_multi(group, filelist, filecount); for (i = 0; i < filecount; i++) { free(filelist[i]); } snprintf(temppath, sizeof(temppath), "%s%c_group_%08X", tempdir, PATH_SEP, group->group_id); if (rmdir(temppath) == -1) { gsyserror(group, "Failed remove temp directory %s", temppath); } }
/** * Sends back a STATUS message with the given NAK list */ void send_status(struct group_list_t *group, unsigned int section, const unsigned char *naks, unsigned int nak_count) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct status_h *status; struct tfmcc_ack_info_he *tfmcc; unsigned char *sent_naks; int payloadlen, enclen; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; status = (struct status_h *)(buf + sizeof(struct uftp_h)); tfmcc = (struct tfmcc_ack_info_he *)((unsigned char *)status + sizeof(struct status_h)); set_uftp_header(header, STATUS, group); status->func = STATUS; if (group->cc_type == CC_TFMCC) { status->hlen = (sizeof(struct status_h) + sizeof(struct tfmcc_ack_info_he)) / 4; } else { status->hlen = sizeof(struct status_h) / 4; } status->file_id = htons(group->file_id); status->section = htons(section); if (section >= group->fileinfo.big_sections) { payloadlen = (group->fileinfo.secsize_small / 8) + 1; } else { payloadlen = (group->fileinfo.secsize_big / 8) + 1; } if (group->cc_type == CC_TFMCC) { set_tfmcc_ack_info(group, tfmcc); } sent_naks = (unsigned char *)status + (status->hlen * 4); memcpy(sent_naks, naks, payloadlen); payloadlen += status->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 STATUS"); 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 STATUS"); } else { glog2(group, "Sent %d NAKs for section %d", nak_count, section); } free(buf); free(encrypted); }
/** * Reads an expected FILESEG and writes it to the proper place in the file */ void handle_fileseg(struct group_list_t *group, const unsigned char *message, unsigned meslen, uint16_t txseq) { const struct fileseg_h *fileseg; const struct tfmcc_data_info_he *tfmcc; const unsigned char *data; const uint8_t *he; int datalen, section, cache_offset, status_idx; uint32_t seq, i; unsigned extlen; if (group->fileinfo.ftype != FTYPE_REG) { glog2(group, "Rejecting FILESEG: not a regular file"); return; } fileseg = (const struct fileseg_h *)message; data = message + (fileseg->hlen * 4); datalen = meslen - (fileseg->hlen * 4); if ((meslen < (fileseg->hlen * 4U)) || ((fileseg->hlen * 4U) < sizeof(struct fileseg_h))) { glog2(group, "Rejecting FILESEG: invalid message size"); return; } if (ntohs(fileseg->file_id) != group->file_id) { glog2(group, "Rejecting FILESEG: got incorrect file_id %04X", ntohs(fileseg->file_id)); return; } tfmcc = NULL; if (fileseg->hlen * 4U > sizeof(struct fileseg_h)) { he = (const uint8_t *)fileseg + sizeof(struct fileseg_h); if (*he == EXT_TFMCC_DATA_INFO) { tfmcc = (const struct tfmcc_data_info_he *)he; extlen = tfmcc->extlen * 4U; if ((extlen > (fileseg->hlen * 4U) - sizeof(struct fileseg_h)) || extlen < sizeof(struct tfmcc_data_info_he)) { glog2(group, "Rejecting FILESEG: invalid extension size"); return; } } } section = ntohs(fileseg->section); if (section >= group->fileinfo.big_sections) { seq = (group->fileinfo.big_sections * group->fileinfo.secsize_big) + ((section - group->fileinfo.big_sections) * group->fileinfo.secsize_small) + ntohs(fileseg->sec_block); } else { seq = (section * group->fileinfo.secsize_big) + ntohs(fileseg->sec_block); } if ((datalen != group->blocksize) && (seq != group->fileinfo.blocks - 1)) { glog2(group, "Rejecting FILESEG: invalid data size %d", datalen); return; } if (log_level >= 5) { glog5(group, "Got packet %d", seq); } else if (log_level == 4) { if (seq != group->fileinfo.last_block + 1) { glog4(group, "Got packet %d, last was %d", seq, group->fileinfo.last_block); } } if ((group->cc_type == CC_TFMCC) && tfmcc) { handle_tfmcc_data_info(group, tfmcc); } group->fileinfo.got_data = 1; group->fileinfo.last_block = seq; if (txseq == group->max_txseq) { if ((section > group->fileinfo.last_section) && (group->fileinfo.nak_time.tv_sec == 0)) { // Start timer to send NAKs gettimeofday(&group->fileinfo.nak_time, NULL); add_timeval_d(&group->fileinfo.nak_time, 1 * group->grtt); group->fileinfo.nak_section_first = group->fileinfo.last_section; group->fileinfo.nak_section_last = section; group->fileinfo.got_done = 0; glog3(group, "New section, set NAK timer for sections %d - %d", group->fileinfo.nak_section_first, group->fileinfo.nak_section_last); } group->fileinfo.last_section = section; } if (group->fileinfo.naklist[seq]) { if ((seq >= group->fileinfo.cache_start) && (seq <= group->fileinfo.cache_end + MAXMISORDER)) { cache_offset=(seq - group->fileinfo.cache_start) * group->blocksize; if (seq > group->fileinfo.cache_end) { if ((cache_offset + datalen) > cache_len) { glog4(group, "Disk cache full, flushing"); if (!flush_disk_cache(group)) { return; } cache_offset = (seq - group->fileinfo.cache_start) * group->blocksize; } else { for (i = group->fileinfo.cache_end; i <= seq; i++) { if (!group->fileinfo.naklist[i]) { glog3(group, "Cache gap seq %d " "already received, flushing", i); if (!flush_disk_cache(group)) { return; } group->fileinfo.cache_start = seq; cache_offset = 0; break; } } group->fileinfo.cache_end = seq; } } } else { if (group->fileinfo.cache_len != 0) { glog3(group, "Seq %d out of cache range, flushing", seq); if (!flush_disk_cache(group)) { return; } } cache_offset = 0; group->fileinfo.cache_start = seq; group->fileinfo.cache_end = seq; } group->fileinfo.cache_len = ((group->fileinfo.cache_end - group->fileinfo.cache_start) * group->blocksize) + datalen; status_idx = seq - group->fileinfo.cache_start; if (group->fileinfo.cache_len > cache_len) { glog0(group, "Cache overrun: " "current cache len = %d, status_idx = %d", group->fileinfo.cache_len, status_idx); } group->fileinfo.cache_status[status_idx] = 1; memcpy(&group->fileinfo.cache[cache_offset], data, datalen); } set_timeout(group, 0); }
/** * Perform FILEINFO processing specific to a regular file */ void handle_fileinfo_regular(struct group_list_t *group) { // First handle restart or sync mode, // then create/open the file. if (group->restartinfo) { if (handle_fileinfo_restart(group)) { return; } } else if (group->sync_mode) { if (handle_fileinfo_sync(group)) { return; } } if (group->fileinfo.restart) { group->fileinfo.fd = open(group->fileinfo.filepath, OPENWRITE); } else { const char *filename; if (tempfile) { filename = group->fileinfo.temppath; } else { filename = group->fileinfo.filepath; } #ifdef WINDOWS SetFileAttributes(filename, FILE_ATTRIBUTE_NORMAL); #else chmod(filename, 0644); #endif group->fileinfo.fd = open(filename, OPENWRITE | O_CREAT | O_TRUNC,0644); } if (group->fileinfo.fd == -1) { gsyserror(group, "Error opening data file"); early_complete(group, COMP_STAT_REJECTED, 0); return; } if (group->fileinfo.size > free_space(group->fileinfo.filepath)) { glog0(group, "Not enough disk space, aborting"); send_abort(group, "Not enough disk space"); return; } // Final preparations for receiving a file. if (group->fileinfo.restart) { group->fileinfo.naklist = group->restartinfo->naklist; group->fileinfo.section_done = group->restartinfo->section_done; group->restartinfo->naklist = NULL; group->restartinfo->section_done = NULL; free(group->restartinfo); group->restartinfo = NULL; } else { group->fileinfo.naklist = safe_calloc(group->fileinfo.blocks, 1); group->fileinfo.section_done = safe_calloc(group->fileinfo.sections, 1); memset(group->fileinfo.naklist, 1, group->fileinfo.blocks); } group->fileinfo.last_block = -1; group->fileinfo.last_section = 0; group->fileinfo.curr_offset = 0; group->fileinfo.cache_start = 0; group->fileinfo.cache_end = 0; group->fileinfo.cache_len = 0; group->fileinfo.cache = safe_calloc(cache_len, 1); group->fileinfo.cache_status = safe_calloc(cache_len / group->blocksize, 1); group->phase = PHASE_RECEIVING; send_fileinfo_ack(group, group->fileinfo.restart); set_timeout(group, 0); }
/** * Reads in the contents of the restart file. */ void read_restart_file(struct group_list_t *group) { struct client_restart_t *restart; char restart_name[MAXPATHNAME]; int fd, i, rval; // Don't bother if we're not using a temp directory. if (!strcmp(tempdir, "")) { return; } // First abort any prior session with the same group_id. // This creates the restart file. for (i = 0; i < MAXLIST; i++) { if ((group_list[i].group_id == group->group_id) && (group_list[i].group_inst < group->group_inst)) { file_cleanup(&group_list[i], 1); } } glog2(group, "Reading restart file"); snprintf(restart_name, sizeof(restart_name), "%s%c_group_%08X_restart", tempdir, PATH_SEP, group->group_id); if ((fd = open(restart_name, OPENREAD, 0644)) == -1) { gsyserror(group, "Failed to read restart file"); return; } // Read header restart = safe_calloc(sizeof(struct client_restart_t), 1); if ((rval = file_read(fd, restart, sizeof(struct client_restart_t), 0)) == -1) { glog0(group, "Failed to read header for restart file"); goto err1; } if (rval != sizeof(struct client_restart_t)) { glog0(group, "Failed to read header for restart file " "(read %d, expected %d)", rval,sizeof(struct client_restart_t)); goto err1; } // Read NAK list if (restart->blocks) { restart->naklist = safe_calloc(restart->blocks, 1); if (file_read(fd, restart->naklist, restart->blocks, 0) == -1) { glog0(group, "Failed to read NAK list for restart file"); goto err2; } } // Read section_done list if (restart->sections) { restart->section_done = safe_calloc(restart->sections, 1); if (file_read(fd, restart->section_done, restart->sections, 0) == -1) { glog0(group, "Failed to read section_done list for restart file"); goto err3; } } close(fd); unlink(restart_name); group->restartinfo = restart; glog3(group, "Reading restart file done"); return; err3: free(restart->section_done); err2: free(restart->naklist); err1: free(restart); close(fd); }
/** * Sends a FILEINFO_ACK in response to a FILEINFO */ void send_fileinfo_ack(struct group_list_t *group, int restart) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct fileinfoack_h *fileinfo_ack; struct timeval now, send_time; unsigned int payloadlen; int enclen; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; fileinfo_ack = (struct fileinfoack_h *)(buf + sizeof(struct uftp_h)); payloadlen = sizeof(struct fileinfoack_h); set_uftp_header(header, FILEINFO_ACK, group); fileinfo_ack->func = FILEINFO_ACK; fileinfo_ack->hlen = sizeof(struct fileinfoack_h) / 4; fileinfo_ack->file_id = htons(group->file_id); if (restart) { fileinfo_ack->flags |= FLAG_PARTIAL; } 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)); } fileinfo_ack->tstamp_sec = htonl((uint32_t)send_time.tv_sec); fileinfo_ack->tstamp_usec = htonl((uint32_t)send_time.tv_usec); if (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 FILEINFO_ACK"); 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 FILEINFO_ACK"); } else { glog2(group, "FILEINFO_ACK sent"); } glog3(group, "send time: %d.%06d", send_time.tv_sec, send_time.tv_usec); free(encrypted); free(buf); }
/** * Calculate the master key and do key expansion to determine the symmetric * cypher key and IV salt, and hash key for the server */ int calculate_server_keys(struct group_list_t *group, const struct enc_info_he *encinfo) { unsigned char *seed, *prf_buf; int explen, len, seedlen; time_t t; uint32_t t2; memcpy(group->rand1, encinfo->rand1, sizeof(encinfo->rand1)); if (!get_random_bytes(group->rand2, sizeof(group->rand2))) { glog0(group, "Failed to get random bytes for rand2"); send_abort(group, "Failed to get random bytes for rand2"); return 0; } // Sets the first 4 bytes of rand2 to the current time t = time(NULL); t2 = (uint32_t)(t & 0xFFFFFFFF); *(uint32_t *)(group->rand2) = t2; if (group->keyextype == KEYEX_RSA) { if (!get_random_bytes(group->premaster, MASTER_LEN)) { glog0(group, "Failed to get random bytes for premaster"); send_abort(group, "Failed to get random bytes for premaster"); return 0; } group->premaster_len = MASTER_LEN; } else { EC_key_t pubecdh; if (has_proxy) { pubecdh = proxy_dhkey.ec; } else { pubecdh = group->server_dhkey.ec; } if (!get_ECDH_key(pubecdh, group->client_dhkey.ec, group->premaster, &group->premaster_len)) { glog0(group, "Failed to calculate ECDH key"); send_abort(group, "Failed to calculate ECDH key"); return 0; } } get_key_info(group->keytype, &group->keylen, &group->ivlen); group->hmaclen = get_hash_len(group->hashtype); explen = group->keylen + SALT_LEN + group->hmaclen; seedlen = RAND_LEN * 2; seed = safe_calloc(seedlen, 1); prf_buf = safe_calloc(MASTER_LEN + explen + group->hmaclen, 1); memcpy(seed, group->rand1, sizeof(group->rand1)); memcpy(seed + sizeof(group->rand1), group->rand2, sizeof(group->rand2)); PRF(group->hashtype, MASTER_LEN, group->premaster, group->premaster_len, "master secret", seed, seedlen, prf_buf, &len); memcpy(group->master,prf_buf, sizeof(group->master)); PRF(group->hashtype, explen, group->master, sizeof(group->master), "key expansion", seed, seedlen, prf_buf, &len); memcpy(group->hmackey, prf_buf, group->hmaclen); memcpy(group->key, prf_buf + group->hmaclen, group->keylen); memcpy(group->salt, prf_buf + group->hmaclen + group->keylen, SALT_LEN); free(seed); free(prf_buf); 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!"); } }
/** * Sends a KEYINFO to each client that the server sent a REG_CONF for. */ void send_keyinfo(struct pr_group_list_t *group, const uint32_t *addrlist, int addrlen) { unsigned char *buf, *iv; struct uftp_h *header; struct keyinfo_h *keyinfo_hdr; struct destkey *keylist; unsigned int payloadlen, len; int maxdest, packetcnt, dests, iv_init, foundaddr, i, j; int unauth_keytype, unauth_keylen, unauth_ivlen; struct pr_destinfo_t *dest; // Don't use a cipher in an authentication mode to encrypt the group master unauth_keytype = unauth_key(group->keytype); get_key_info(unauth_keytype, &unauth_keylen, &unauth_ivlen); buf = safe_calloc(MAXMTU, 1); iv = safe_calloc(unauth_ivlen, 1); header = (struct uftp_h *)buf; keyinfo_hdr = (struct keyinfo_h *)(buf + sizeof(struct uftp_h)); keylist= (struct destkey *)((char *)keyinfo_hdr + sizeof(struct keyinfo_h)); set_uftp_header(header, KEYINFO, group); keyinfo_hdr->func = KEYINFO; keyinfo_hdr->hlen = sizeof(struct keyinfo_h) / 4; iv_init = 0; maxdest = max_msg_dest(group, KEYINFO, keyinfo_hdr->hlen * 4); packetcnt = 1; for (i = 0, dests = 0; i < group->destcount; i++) { dest = &group->destinfo[i]; if (dest->state == PR_CLIENT_CONF) { if (addrlist) { // We just got a REG_CONF, so only send to listed hosts for (j = 0, foundaddr = 0; (j < addrlen) && (!foundaddr); j++) { if (dest->id == addrlist[j]) { foundaddr = 1; } } } else { foundaddr = 1; } if (foundaddr) { if (!iv_init) { group->ivctr++; keyinfo_hdr->iv_ctr_hi = htonl((group->ivctr & 0xFFFFFFFF00000000LL) >> 32); keyinfo_hdr->iv_ctr_lo = htonl(group->ivctr & 0x00000000FFFFFFFFLL); iv_init = 1; } keylist[dests].dest_id = dest->id; build_iv(iv, dest->salt, unauth_ivlen, uftp_htonll(group->ivctr), group->src_id); if (!encrypt_block(unauth_keytype, iv, dest->key, NULL, 0, &group->groupmaster[1], sizeof(group->groupmaster) - 1, keylist[dests].groupmaster, &len)) { glog0(group, "Error encrypting KEYINFO for %s", dest->name); free(buf); free(iv); return; } dests++; } } if ((dests >= maxdest) || ((i == group->destcount - 1) && (dests > 0))) { payloadlen = sizeof(struct keyinfo_h) + (dests * sizeof(struct destkey)); glog2(group,"Sending KEYINFO %d.%d", group->keyinfo_cnt, packetcnt); if (nb_sendto(listener, buf, payloadlen + sizeof(struct uftp_h), 0, (struct sockaddr *)&group->privatemcast, family_len(group->privatemcast)) == SOCKET_ERROR) { gsockerror(group, "Error sending KEYINFO"); free(buf); free(iv); return; } // TODO: This value is good for around 100Mbps. This is under the // assumption that the client proxy is local to the clients // it serves. This should probably be a parameter. usleep(120); memset(keylist, 0, maxdest * sizeof(struct destkey)); iv_init = 0; dests = 0; packetcnt++; } }