uftp-4.1.5/0000755000076400007640000000000012304524273011505 5ustar dbushdbushuftp-4.1.5/ReadMe.txt0000644000076400007640000001402712176066071013413 0ustar dbushdbush--== UFTP - Encrypted UDP based FTP with multicast ==-- UFTP is an encrypted multicast file transfer program, designed to securely, reliably, and efficiently transfer files to multiple receivers simultaneously. This is useful for distributing large files to a large number of receivers, and is especially useful for data distribution over a satellite link (with two way communication), where the inherent delay makes any TCP based communication highly inefficient. The multicast encryption scheme is based on TLS with extensions to allow multiple receivers to share a common key. UFTP also has the capability to communicate over disjoint networks separated by one or more firewalls (NAT traversal) and without full end-to-end multicast capability (multicast tunneling) through the use of a UFTP proxy server. These proxies also provide scalability by aggregating responses from a group of receivers. --== Building ==-- UNIX-like systems require GNU make and a C compiler such as GCC or equivalent. Windows systems require Visual Studio Express 2008 or later. Non-Windows systems require OpenSSL to be installed if encryption support is enabled. On Linux, Solaris, and BSD systems (including MacOSX), this should be included with the OS. To compile for UNIX-like systems, including MacOSX: make [ OPENSSL={install directory for OpenSSL} ] [ NO_ENCRYPTION=1 ] [ NO_EC=1 ] To compile for Windows (from a Visual Studio command prompt): nmake -f makefile.mak [ OPENSSL={install directory for OpenSSL} ] [ NO_ENCRYPTION=1 ] [ WINXP=1 ] The OPENSSL parameter to make should only need to be specified if OpenSSL is installed in a non-standard location, or on systems where it isn't preinstalled. The NO_ENCRYPTION flag compiles with no encryption support. This can be useful in embedded environments that don't need encryption and want to keep the size of the executable down, and for use in a Windows service that doesn't require encryption. The NO_EC flag compiles without support for Elliptic Curve crypto. This may be required if either you have an older version of OpenSSL or the build of OpenSSL installed wasn't configured with ECC support. The WINXP flag is used on Windows systems to disable features requiring Windows Vista or Windows 7. This includes dual stack IPv4/IPv6 sockets as well as the CNG API which supports Elliptic Curve crypto, larger hash sizes, and authenticated mode ciphers. To install for UNIX-like systems, including MacOSX: make [ DESTDIR={install directory} ] install The DESTDIR parameter allows installing into a fake root directory, which can be useful for packaging utilities such as rpm. --== Tuning ==-- If you find that clients can't receive data fast enough, or if servers communicating with several hundred clients can't handle the flood of STATUS messages that come in, you can increase the UDP send/receive buffer size to help with this. This is set using the -B option on server, client, or proxy. However, many operating systems impose a maximum value of 256K (262144). This limit can be increased, although the method is OS specific. Here are a few common ones: Solaris: ndd -set /dev/udp udp_max_buf {value} Linux: sysctl -w net.core.rmem_max={value} sysctl -w net.core.wmem_max={value} MacOSX / FreeBSD: sysctl -w kern.ipc.maxsockbuf={value} (The actual maximum you can use is 8/9 of this value) While Windows does not have this limitation, Windows XP Service Pack 3 and later does throttle back UDP datagrams larger that 1024 bytes by default (a UFTP packet is 1472 bytes by default). This is most commonly seen when attempting to move data at 100 Mbps or more. You can change this by adding/modifying the following registry value: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters\FastSendDatagramThreshold Set this DWORD value to 1500 and reboot, and it should take care of the issue. If you're making use of the DSCP/TOS byte in the IP header, this can be modified via the -Q option to the server, client, or proxy. On Windows XP systems, this can't be modified by default using portable libriaries. This can be overridden by adding/modifying the following registry value: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DisableUserTOSSetting Set this DWORD value to 0 and reboot, and the -Q option should work. Windows Vista and Windows 7 don't support this particular mechanism at all, and each has it's own way of doing it. For now, this option does not work on these OSs. --== Running under Windows ==-- UFTP was originally written to run in a UNIX/Linux environment, so it doens't natively do things that other Windows servers do such as run in the background or run as a Windows service. Included with UFTP are hidedos.exe, instsrv.exe, and srvany.exe which allows these things to happen. Hidedos is a utility originally written by LANDesk to allow command line windows programs to run in the background. Instsrv and srvany are components of the Windows Resource Kit which. Instsrv is used to install and remove services, and srvany allows programs not written as windows services to run as such. To run the uftpd client or proxy without leaving a command prompt open (the -d option tells uftpd to run in the foreground, so don't use this option in this case): hidedos uftpd [options] -- or -- hidedos uftpproxyd [options] To run the uftpd client as a Windows service: (steps for the proxy are similar) Copy instsrv.exe and srvany.exe to the system folder (usually C:\Windows\system32) Install the service: instsrv UFTPD c:\windows\system32\srvany.exe Configure the service: Open regedit and go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\UFTPD Right-click the UFTPD key and choose New > Key. Name the key Parameters. Right-click the new Parameters key under UFTP and choose New > String Value. Name the string Application. Double-click the Application value and fill in the full command line to uftpd Once configured, the service should start. In the task list, you'll see both srvany.exe and uftpd.exe running while the service is up. If you need to remove the service: instsrv UFTPD remove uftp-4.1.5/win_func.c0000644000076400007640000000556712137013343013471 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2011 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include "win_func.h" int optind=1; char *optarg; char getopt(int argc, char *argv[], const char options[]) { char *loc; if (optind >= argc) { return -1; } else if (strlen(argv[optind]) <= 1) { return -1; } else if (argv[optind][0] != '-') { return -1; } else if (argv[optind][1] == '-') { optind++; return -1; } else if (!isalnum(argv[optind][1])) { return '?'; } else if ((loc = strchr(options, argv[optind][1])) == NULL) { return '?'; } else { optind++; if (loc[1] == ':') { optarg = argv[optind++]; } return loc[0]; } } int gettimeofday(struct timeval *tv, struct timezone *tz) { const unsigned __int64 epoch_diff = 11644473600000000; unsigned __int64 tmp; FILETIME t; if (tv) { GetSystemTimeAsFileTime(&t); tmp = 0; tmp |= t.dwHighDateTime; tmp <<= 32; tmp |= t.dwLowDateTime; tmp /= 10; tmp -= epoch_diff; tv->tv_sec = (long)(tmp / 1000000); tv->tv_usec = (long)(tmp % 1000000); } return 0; } int get_win_priority(int priority) { switch (priority) { case -2: return HIGH_PRIORITY_CLASS; case -1: return ABOVE_NORMAL_PRIORITY_CLASS; case 0: return NORMAL_PRIORITY_CLASS; case 1: return BELOW_NORMAL_PRIORITY_CLASS; case 2: return IDLE_PRIORITY_CLASS; default: return NORMAL_PRIORITY_CLASS; } }uftp-4.1.5/client_main.c0000644000076400007640000000304112137013343014124 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2010 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include "client_config.h" #include "client_init.h" #include "client_loop.h" int main(int argc, char *argv[]) { pre_initialize(); process_args(argc, argv); initialize(); mainloop(); return 0; } uftp-4.1.5/encrypt_cng.c0000644000076400007640000012330312250244021014154 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ //#include #include #include // Use this #define to pull in neccesary declarations from uftp_common.h // in encryption.h instead. We don't include uftp_common.h because it contains // an include for winsock2.h which is needed pretty much everyplace else. // Including winsock2.h prevents BCryptEncrypt and BCryptDecrypt from working // with GCM and CCM mode ciphers. #define NO_UFTP_COMMON_H #include "uftp.h" #include "encryption.h" #define MAXLIST 100 #define BLOBLEN 1000 static struct providers_t { LPCWSTR alg; LPCWSTR mode; int hmac; BCRYPT_ALG_HANDLE handle; } providers[MAXLIST]; static int provlen; static const struct keyinfo_t { int keytype; int keysize; int blocksize; } keyinfo[] = { { KEY_DES, 8, 8 }, { KEY_DES_EDE3, 24, 8 }, { KEY_AES128_CBC, 16, 16 }, { KEY_AES128_GCM, 16, 12 }, { KEY_AES128_CCM, 16, 12 }, { KEY_AES256_CBC, 32, 16 }, { KEY_AES256_GCM, 32, 12 }, { KEY_AES256_CCM, 32, 12 } }; static int machine_keyset = 0; static int init_done = 0; /** * Prints Microsoft specific error messages to log */ static void mserror(const char *str, int err) { char errbuf[300]; HMODULE Hand = LoadLibrary("NTDLL.DLL"); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, Hand, err, 0, errbuf, sizeof(errbuf), NULL); clog0(0, 0, "%s: (0x%08X) %s", str, err, errbuf); FreeLibrary(Hand); } /** * Performs all necessary steps to initialize the crypto library */ void crypto_init(int set_sys_key) { provlen = 0; if (set_sys_key) { machine_keyset = NCRYPT_MACHINE_KEY_FLAG; } else { machine_keyset = 0; } init_done = 1; } /** * Performs all necessary steps to clean up the crypto library */ void crypto_cleanup(void) { NTSTATUS status; int i; if (!init_done) { return; } for (i = 0; i < provlen; i++) { status = BCryptCloseAlgorithmProvider(providers[i].handle, 0); if (!BCRYPT_SUCCESS(status)) { mserror("BCryptCloseAlgorithmProvider failed", status); } } } /** * Returns the next key for the current user */ const char *get_next_container(void) { static NCRYPT_PROV_HANDLE prov = 0; static NCryptKeyName *keyitem = NULL; static void *ptr = NULL; static char name[256]; SECURITY_STATUS sstatus; if (!prov) { sstatus = NCryptOpenStorageProvider(&prov, NULL, 0); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptOpenStorageProvider failed", sstatus); return NULL; } } if (keyitem) { NCryptFreeBuffer(keyitem); } sstatus = NCryptEnumKeys(prov, NULL, &keyitem, &ptr, NCRYPT_SILENT_FLAG | machine_keyset); if (sstatus == ERROR_SUCCESS) { wcstombs(name, keyitem->pszName, sizeof(name)); return name; } else { if (sstatus != NTE_NO_MORE_ITEMS) { mserror("NCryptEnumKeys failed", sstatus); } NCryptFreeBuffer(keyitem); keyitem = NULL; NCryptFreeBuffer(ptr); ptr = NULL; NCryptFreeObject(prov); prov = 0; return NULL; } } /** * Deletes the key container with the given name */ void delete_container(const char *container) { NCRYPT_PROV_HANDLE prov; NCRYPT_KEY_HANDLE key; SECURITY_STATUS sstatus; wchar_t wcontainer[256]; if (!BCRYPT_SUCCESS(sstatus = NCryptOpenStorageProvider(&prov, NULL, 0))) { mserror("NCryptOpenStorageProvider failed", sstatus); } memset(wcontainer, 0, sizeof(wcontainer)); mbstowcs(wcontainer, container, strlen(container)); sstatus = NCryptOpenKey(prov, &key, wcontainer, 0, NCRYPT_SILENT_FLAG | machine_keyset); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptOpenKey failed", sstatus); NCryptFreeObject(prov); } if (!BCRYPT_SUCCESS(sstatus = NCryptDeleteKey(key, NCRYPT_SILENT_FLAG))) { NCryptFreeObject(prov); mserror("NCryptDeleteKey failed", sstatus); } NCryptFreeObject(prov); } /** * Gets an algorithm provider handle for the given hash or cipher. * Check the provider list to see if it exists. If so, return it, * otherwise get a new one and put it in the list. */ static BCRYPT_ALG_HANDLE get_alg_handle(LPCWSTR alg, LPCWSTR mode, int hmac) { BCRYPT_ALG_HANDLE handle; NTSTATUS status; int i; for (i=0; i siglen) { log0(0, 0, "Invalid signature length"); return 0; } sstatus = NCryptVerifySignature(ec, NULL, meshash, meshashlen, rsval, ntohs(*rlen) + ntohs(*slen), 0); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptVerifySignature failed", sstatus); return 0; } return 1; } /** * Creates an ECDH key based on two EC keys, one public and one private */ int get_ECDH_key(EC_key_t pubkey, EC_key_t privkey, unsigned char *key, unsigned int *keylen) { SECURITY_STATUS sstatus; NCRYPT_SECRET_HANDLE secret; int _len; sstatus = NCryptSecretAgreement(privkey, pubkey, &secret, 0); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptSecretAgreement failed", sstatus); return 0; } sstatus = NCryptDeriveKey(secret, BCRYPT_KDF_HASH, NULL, NULL, 0, &_len, 0); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptDeriveKey failed", sstatus); return 0; } sstatus = NCryptDeriveKey(secret, BCRYPT_KDF_HASH, NULL, key, _len, keylen, 0 ); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptDeriveKey failed", sstatus); return 0; } return 1; } /** * Imports an RSA keyblob into an RSA public key */ int import_RSA_key(RSA_key_t *rsa, const unsigned char *keyblob, uint16_t bloblen) { SECURITY_STATUS sstatus; NCRYPT_PROV_HANDLE prov; BCRYPT_RSAKEY_BLOB *blobheader; char *buf, *buf_exp, *buf_mod; int buflen; const struct rsa_blob_t *rsablob; const unsigned char *modulus; rsablob = (struct rsa_blob_t *)keyblob; modulus = keyblob + sizeof(struct rsa_blob_t); if (sizeof(struct rsa_blob_t) + ntohs(rsablob->modlen) != bloblen) { log0(0, 0, "Error importing RSA key: invalid length"); return 0; } buflen = sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(rsablob->exponent) + ntohs(rsablob->modlen); buf = safe_calloc(buflen, 1); blobheader = (BCRYPT_RSAKEY_BLOB *)buf; buf_exp = buf + sizeof(BCRYPT_RSAKEY_BLOB); buf_mod = buf_exp + sizeof(rsablob->exponent); blobheader->Magic = BCRYPT_RSAPUBLIC_MAGIC; blobheader->BitLength = ntohs(rsablob->modlen) * 8; blobheader->cbPublicExp = sizeof(rsablob->exponent); blobheader->cbModulus = ntohs(rsablob->modlen); buf_exp[0] = (char)((ntohl(rsablob->exponent) & 0xFF000000) >> 24); buf_exp[1] = (char)((ntohl(rsablob->exponent) & 0x00FF0000) >> 16); buf_exp[2] = (char)((ntohl(rsablob->exponent) & 0x0000FF00) >> 8); buf_exp[3] = (char)(ntohl(rsablob->exponent) & 0x000000FF); memcpy(buf_mod, modulus, ntohs(rsablob->modlen)); if (!BCRYPT_SUCCESS(sstatus = NCryptOpenStorageProvider(&prov, NULL, 0 ))) { mserror("NCryptOpenStorageProvider failed", sstatus); free(buf); return 0; } sstatus = NCryptImportKey(prov, 0, BCRYPT_RSAPUBLIC_BLOB, NULL, rsa, buf, buflen, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptImportKey failed", sstatus); free(buf); NCryptFreeObject(prov); return 0; } free(buf); NCryptFreeObject(prov); return 1; } /** * Exports an RSA public key into an RSA keyblob */ int export_RSA_key(const RSA_key_t rsa, unsigned char *keyblob, uint16_t *bloblen) { SECURITY_STATUS sstatus; DWORD len; BCRYPT_RSAKEY_BLOB *blobheader; char *buf, *buf_exp, *buf_mod; struct rsa_blob_t *rsablob; unsigned char *modulus; uint32_t exponent; int i; rsablob = (struct rsa_blob_t *)keyblob; modulus = keyblob + sizeof(struct rsa_blob_t); sstatus = NCryptExportKey(rsa, (NCRYPT_KEY_HANDLE)NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, NULL, 0, &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptExportKey failed", sstatus); return 0; } buf = safe_malloc(len); sstatus = NCryptExportKey(rsa, (NCRYPT_KEY_HANDLE)NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, buf, len, &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptExportKey failed", sstatus); free(buf); return 0; } blobheader = (BCRYPT_RSAKEY_BLOB *)buf; buf_exp = buf + sizeof(BCRYPT_RSAKEY_BLOB); buf_mod = buf_exp + blobheader->cbPublicExp; if (blobheader->cbPublicExp > 4) { log0(0, 0, "unexpected size %d of public exponent\n", blobheader->cbPublicExp); free(buf); return 0; } for (exponent = 0, i = blobheader->cbPublicExp - 1; i >= 0; i--) { exponent |= buf_exp[i] << (8 * (blobheader->cbPublicExp - 1 - i)); } rsablob->blobtype = KEYBLOB_RSA; rsablob->reserved = 0; rsablob->modlen = htons((uint16_t)blobheader->cbModulus); rsablob->exponent = htonl(exponent); memcpy(modulus, buf_mod, blobheader->cbModulus); *bloblen = (uint16_t)(sizeof(struct rsa_blob_t) + blobheader->cbModulus); free(buf); return 1; } /** * Imports an EC keyblob into an EC public key */ int import_EC_key(EC_key_t *ec, const unsigned char *keyblob, uint16_t bloblen, int isdh) { SECURITY_STATUS sstatus; NCRYPT_PROV_HANDLE prov; BCRYPT_ECCKEY_BLOB *blobheader; char *buf, *buf_key; int buflen; const struct ec_blob_t *ecblob; const unsigned char *keyval; ecblob = (struct ec_blob_t *)keyblob; keyval = keyblob + sizeof(struct ec_blob_t); if (sizeof(struct ec_blob_t) + ntohs(ecblob->keylen) > bloblen) { log0(0, 0, "Error importing EC key: invalid length"); return 0; } buflen = sizeof(BCRYPT_ECCKEY_BLOB) + ntohs(ecblob->keylen); buf = safe_calloc(buflen, 1); blobheader = (BCRYPT_ECCKEY_BLOB *)buf; buf_key = buf + sizeof(BCRYPT_ECCKEY_BLOB); blobheader->dwMagic = get_curve_magic(ecblob->curve, isdh); blobheader->cbKey = ntohs(ecblob->keylen) / 2; memcpy(buf_key, keyval, ntohs(ecblob->keylen)); if (!BCRYPT_SUCCESS(sstatus = NCryptOpenStorageProvider(&prov, NULL, 0 ))) { mserror("NCryptOpenStorageProvider failed", sstatus); free(buf); return 0; } sstatus = NCryptImportKey(prov, 0, BCRYPT_ECCPUBLIC_BLOB, NULL, ec, buf, buflen, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptImportKey failed", sstatus); free(buf); NCryptFreeObject(prov); return 0; } free(buf); NCryptFreeObject(prov); return 1; } /** * Exports an EC public key into an EC keyblob */ int export_EC_key(const EC_key_t ec, unsigned char *keyblob, uint16_t *bloblen) { SECURITY_STATUS sstatus; DWORD len; BCRYPT_ECCKEY_BLOB *blobheader; char *buf, *buf_key; struct ec_blob_t *ecblob; unsigned char *keyval; ecblob = (struct ec_blob_t *)keyblob; keyval = keyblob + sizeof(struct ec_blob_t); sstatus = NCryptExportKey(ec, 0, BCRYPT_ECCPUBLIC_BLOB, NULL, NULL, 0, &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptExportKey failed", sstatus); return 0; } buf = safe_malloc(len); sstatus = NCryptExportKey(ec, 0, BCRYPT_ECCPUBLIC_BLOB, NULL, buf, len, &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptExportKey failed", sstatus); free(buf); return 0; } blobheader = (BCRYPT_ECCKEY_BLOB *)buf; buf_key = buf + sizeof(BCRYPT_ECCKEY_BLOB); ecblob->blobtype = KEYBLOB_EC; ecblob->curve = get_EC_curve(ec); ecblob->keylen = htons((uint16_t)(blobheader->cbKey * 2)); memcpy(keyval, buf_key, blobheader->cbKey * 2); *bloblen = (uint16_t)(sizeof(struct ec_blob_t) + (blobheader->cbKey * 2)); free(buf); return 1; } /** * Generates an RSA private key with the given exponent and number of bits * and writes it into the specified key container */ RSA_key_t gen_RSA_key(int bits, int exponent, const char *container) { NCRYPT_PROV_HANDLE prov; NCRYPT_KEY_HANDLE key; SECURITY_STATUS sstatus; wchar_t wcontainer[256]; if (!BCRYPT_SUCCESS(sstatus = NCryptOpenStorageProvider(&prov, NULL, 0))) { mserror("NCryptOpenStorageProvider failed", sstatus); return 0; } memset(wcontainer, 0, sizeof(wcontainer)); if (container && strcmp(container, "")) { mbstowcs(wcontainer, container, strlen(container)); } sstatus = NCryptCreatePersistedKey(prov, &key, BCRYPT_RSA_ALGORITHM, (!wcscmp(wcontainer, L"") ? NULL : wcontainer), 0, machine_keyset); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptCreatePersistedKey failed", sstatus); NCryptFreeObject(prov); return 0; } if (!bits) bits = DEF_RSA_LEN; sstatus = NCryptSetProperty(key, NCRYPT_LENGTH_PROPERTY, (PBYTE)&bits, sizeof(bits), NCRYPT_PERSIST_FLAG | NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptSetProperty failed", sstatus); NCryptFreeObject(prov); return 0; } sstatus = NCryptFinalizeKey(key, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptFinalizeKey failed", sstatus); NCryptFreeObject(prov); return 0; } NCryptFreeObject(prov); return key; } /** * Loads an RSA private key from the specified key container */ RSA_key_t read_RSA_key(const char *container) { union key_t key; int keytype; key = read_private_key(container, &keytype); if (keytype != KEYBLOB_RSA) { log0(0, 0, "%s not an RSA key", container); NCryptFreeObject(key.rsa); return 0; } return key.rsa; } EC_key_t gen_EC_key(uint8_t curve, int isdh, const char *container) { NCRYPT_PROV_HANDLE prov; NCRYPT_KEY_HANDLE key; SECURITY_STATUS sstatus; LPCWSTR alg; wchar_t wcontainer[256]; alg = get_curve_alg(curve, isdh); if (!alg) { log0(0, 0, "curve not supported\n"); return 0; } if (!BCRYPT_SUCCESS(sstatus = NCryptOpenStorageProvider(&prov, NULL, 0))) { mserror("NCryptOpenStorageProvider failed", sstatus); return 0; } memset(wcontainer, 0, sizeof(wcontainer)); if (container && strcmp(container, "")) { mbstowcs(wcontainer, container, strlen(container)); } sstatus = NCryptCreatePersistedKey(prov, &key, alg, (!wcscmp(wcontainer, L"") || isdh ? NULL : wcontainer), 0, machine_keyset); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptCreatePersistedKey failed", sstatus); NCryptFreeObject(prov); return 0; } if (!BCRYPT_SUCCESS(sstatus = NCryptFinalizeKey(key, NCRYPT_SILENT_FLAG))) { mserror("NCryptFinalizeKey failed", sstatus); NCryptFreeObject(prov); return 0; } NCryptFreeObject(prov); return key; } EC_key_t read_EC_key(const char *container) { union key_t key; int keytype; key = read_private_key(container, &keytype); if (keytype != KEYBLOB_EC) { log0(0, 0, "%s not an EC key", container); NCryptFreeObject(key.ec); return 0; } return key.ec; } union key_t read_private_key(const char *container, int *keytype) { union key_t tmp; NCRYPT_PROV_HANDLE prov; NCRYPT_KEY_HANDLE key; SECURITY_STATUS sstatus; wchar_t algtype[20]; wchar_t wcontainer[256]; int len; tmp.key = 0; *keytype = 0; if (!BCRYPT_SUCCESS(sstatus = NCryptOpenStorageProvider(&prov, NULL, 0))) { mserror("NCryptOpenStorageProvider failed", sstatus); return tmp; } memset(wcontainer, 0, sizeof(wcontainer)); mbstowcs(wcontainer, container, strlen(container)); sstatus = NCryptOpenKey(prov, &key, wcontainer, 0, NCRYPT_SILENT_FLAG | machine_keyset); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptOpenKey failed", sstatus); NCryptFreeObject(prov); return tmp; } sstatus = NCryptGetProperty(key, NCRYPT_ALGORITHM_GROUP_PROPERTY, NULL, 0, &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptGetProperty failed", sstatus); NCryptFreeObject(key); NCryptFreeObject(prov); return tmp; } sstatus = NCryptGetProperty(key, NCRYPT_ALGORITHM_GROUP_PROPERTY, (PBYTE)algtype, sizeof(algtype), &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptGetProperty failed", sstatus); NCryptFreeObject(key); NCryptFreeObject(prov); return tmp; } if (!wcscmp(algtype, NCRYPT_ECDSA_ALGORITHM_GROUP)) { tmp.ec = key; *keytype = KEYBLOB_EC; } else if (!wcscmp(algtype, NCRYPT_RSA_ALGORITHM_GROUP)) { tmp.rsa = key; *keytype = KEYBLOB_RSA; } else { log0(0, 0, "Unexpected key type: %ws", algtype); NCryptFreeObject(key); } NCryptFreeObject(prov); return tmp; } uint8_t get_EC_curve(const EC_key_t ec) { SECURITY_STATUS sstatus; wchar_t alg[20]; int len; sstatus = NCryptGetProperty(ec, NCRYPT_ALGORITHM_PROPERTY, NULL, 0, &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptGetProperty failed", sstatus); return 0; } sstatus = NCryptGetProperty(ec, NCRYPT_ALGORITHM_PROPERTY, (PBYTE)alg, sizeof(alg), &len, NCRYPT_SILENT_FLAG); if (!BCRYPT_SUCCESS(sstatus)) { mserror("NCryptGetProperty failed", sstatus); return 0; } if (!wcscmp(alg, BCRYPT_ECDH_P256_ALGORITHM) || !wcscmp(alg, BCRYPT_ECDSA_P256_ALGORITHM)) { return CURVE_prime256v1; } else if (!wcscmp(alg, BCRYPT_ECDH_P384_ALGORITHM) || !wcscmp(alg, BCRYPT_ECDSA_P384_ALGORITHM)) { return CURVE_secp384r1; } else if (!wcscmp(alg, BCRYPT_ECDH_P521_ALGORITHM) || !wcscmp(alg, BCRYPT_ECDSA_P521_ALGORITHM)) { return CURVE_secp521r1; } else { log0(0, 0, "Unexpected key type: %ws", alg); return 0; } } void free_RSA_key(RSA_key_t rsa) { SECURITY_STATUS sstatus; if (!BCRYPT_SUCCESS(sstatus = NCryptFreeObject(rsa))) { mserror("NCryptFreeObject failed", sstatus); } } void free_EC_key(EC_key_t ec) { SECURITY_STATUS sstatus; if (!BCRYPT_SUCCESS(sstatus = NCryptFreeObject(ec))) { mserror("NCryptFreeObject failed", sstatus); } } void set_sys_keys(int set_sys_key) { if (set_sys_key) { machine_keyset = NCRYPT_MACHINE_KEY_FLAG; } else { machine_keyset = 0; } } uftp-4.1.5/client_loop.h0000644000076400007640000000265112250244021014157 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_LOOP_H #define _CLIENT_LOOP_H void mainloop(void); #endif // _CLIENT_LOOP_H uftp-4.1.5/encryption.h0000644000076400007640000001445512250244021014047 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _ENCRYPTION_H #define _ENCRYPTION_H // This section includes items normally listed in uftp_common.h // that are required in encrypt_cng.c. See encrypt_cng.c for more details. #ifdef NO_UFTP_COMMON_H #include extern int showtime; extern FILE *applog; extern int log_level; void logfunc(uint32_t group_id, uint16_t file_id, int level, int _showtime, int newline, int err, int sockerr, const char *str, ...); #define clog0(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 0, 0, 0, __VA_ARGS__) #define log0(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 1, 0, 0, __VA_ARGS__) #define sclog2(...) \ logfunc(0, 0, 2, 0, 0, 0, 0, __VA_ARGS__) #define syserror(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 1, errno, 0, __VA_ARGS__) int is_auth_enc(int keytype); int is_gcm_mode(int keytype); int is_ccm_mode(int keytype); void *safe_malloc(size_t size); void *safe_calloc(size_t num, size_t size); #endif #ifdef NO_ENCRYPTION typedef void *RSA_key_t; typedef void *EC_key_t; #elif defined WINDOWS && !defined OPENSSL &&\ (_WIN32_WINNT >= _WIN32_WINNT_LONGHORN) #include #include #include typedef NCRYPT_KEY_HANDLE RSA_key_t; typedef NCRYPT_KEY_HANDLE EC_key_t; #elif defined WINDOWS && !defined OPENSSL #include #include typedef HCRYPTKEY RSA_key_t; typedef void *EC_key_t; #else #include typedef RSA *RSA_key_t; #ifndef NO_EC #include #include #include typedef EC_KEY *EC_key_t; #else typedef void *EC_key_t; #endif #endif union key_t { uint64_t key; RSA_key_t rsa; EC_key_t ec; }; void crypto_init(int set_sys_key); void crypto_cleanup(void); int cipher_supported(int keytype); int hash_supported(int hashtype); void get_key_info(int keytype, int *keylen, int *ivlen); int get_hash_len(int hashtype); int get_random_bytes(unsigned char *buf, int num); int encrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen); int decrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen); int create_hmac(int hashtype, const unsigned char *key, unsigned int keylen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen); int hash(int hashtype, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen); int RSA_keylen(const RSA_key_t rsa); int EC_keylen(const EC_key_t ec); int ECDSA_siglen(const EC_key_t ec); int RSA_encrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen); int RSA_decrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen); int create_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen); int verify_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int siglen); int create_ECDSA_sig(EC_key_t ec, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen); int verify_ECDSA_sig(EC_key_t ec, int hashtype, const unsigned char *mes, unsigned int meslen, const unsigned char *sig, unsigned int siglen); int get_ECDH_key(EC_key_t pubkey, EC_key_t privkey, unsigned char *key, unsigned int *keylen); int import_RSA_key(RSA_key_t *rsa, const unsigned char *keyblob, uint16_t bloblen); int export_RSA_key(const RSA_key_t rsa, unsigned char *keyblob, uint16_t *bloblen); int import_EC_key(EC_key_t *ec, const unsigned char *keyblob, uint16_t bloblen, int isdh); int export_EC_key(const EC_key_t ec, unsigned char *keyblob, uint16_t *bloblen); RSA_key_t gen_RSA_key(int bits, int exponent, const char *filename); RSA_key_t read_RSA_key(const char *filename); EC_key_t gen_EC_key(uint8_t curve, int isdh, const char *filename); EC_key_t read_EC_key(const char *filename); union key_t read_private_key(const char *filename, int *keytype); uint8_t get_EC_curve(const EC_key_t ec); void free_RSA_key(RSA_key_t rsa); void free_EC_key(EC_key_t ec); const char *get_next_container(void); void delete_container(const char *name); void set_sys_keys(int set); #endif // _ENCRYPTION_H uftp-4.1.5/server_config.h0000644000076400007640000000621712176066071014523 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_CONFIG_H #define _SERVER_CONFIG_H /** * Default command line values */ #define DEF_RATE 128000 #define DEF_PORT "1044" #define DEF_SRCPORT "0" #define DEF_PUB_MULTI "230.4.4.1" #define DEF_PRIV_MULTI "230.5.5.x" #define DEF_TTL 1 #define DEF_DSCP 0 #define DEF_RCVBUF 262144 #define DEF_BSD_RCVBUF 233016 #define DEF_BLOCKSIZE 1300 #define DEF_GRTT 0.5 #define DEF_MIN_GRTT 0.01 #define DEF_MAX_GRTT 15.0 #define DEF_ROBUST 20 #define DEF_TXWEIGHT 0 #define DEF_MAX_NAK_PCT 100 #define DEF_MAX_NAK_CNT 1 #define DEF_KEYEXTYPE KEYEX_RSA #define DEF_KEYTYPE KEY_NONE #define DEF_HASHTYPE HASH_SHA1 #define DEF_SIGTYPE SIG_HMAC #define USAGE "uftp [ -R txrate ] [ -L logfile ] [ -B udp_buf_size ]\n\ [ -g max_log_size ] [ -n max_log_count ] [ -m max_nak_count ]\n\ [ -Y keytype ] [ -h hashtype ] [ -w sigtype ] [ -e keyextype[:curve] ]\n\ [ -c ] [ -k key_file ] [ -K new_key_length | curve ] [ -l ] [ -T ]\n\ [ -b block_size ] [ -t ttl ] [ -Q dscp ] [ -z | -Z ] [ -I interface ]\n\ [ -p port ] [ -u source_port ] [ -j proxylist_file ]\n\ [ -q ] [ -f ] [ -y ] [ -x log_level ]\n\ [ -H host[,host...] | -H @hostlist_file | -F restart_file ] [ -o ]\n\ [ -X exclude_file ] [ -M pub_multicast_addr ] [ -P priv_multicast_addr ]\n\ [ -C cc_type ] [ -D dest_name ] [ -E base_dir[,base_dir... ] ]\n\ [ -S status_file ] [ -r init_grtt[:min_grtt:max_grtt] ] [ -s robust ] \n\ [ -W txweight ] [ -N max_nak_pct ] { -i list_file | file [ file... ] }\n" void process_args(int argc, char *argv[]); int read_cc_config(const char *filename); #endif // _SERVER_CONFIG_H uftp-4.1.5/server_phase.c0000644000076400007640000014501112250244021014327 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #ifdef WINDOWS #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #endif #include "server.h" #include "server_config.h" #include "server_common.h" #include "server_announce.h" #include "server_transfer.h" /** * Check an announce phase message and pass to appropriate message handler, * decrypting first if necessary * Returns 1 on success, 0 on error */ int handle_announce_phase(unsigned char *packet, unsigned char *decrypted, int packetlen, const union sockaddr_u *receiver, struct finfo_t *finfo, int announce, int open_group, int regconf) { struct uftp_h *header; unsigned char *message; int hostidx; unsigned decryptlen, meslen; uint8_t *func; header = (struct uftp_h *)packet; hostidx = find_client(header->src_id); if ((keytype != KEY_NONE) && (header->func == ENCRYPTED)) { if (hostidx == -1) { log2(finfo->group_id, finfo->file_id, "Got encrypted packet " "from unknown receiver %08X", ntohl(header->src_id)); return 0; } if (!validate_and_decrypt(packet, packetlen, &decrypted, &decryptlen, keytype, groupkey, groupsalt, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, destlist[hostidx].encinfo->pubkey, destlist[hostidx].encinfo->pubkeylen)) { log1(finfo->group_id, finfo->file_id, "Rejecting message from %s: " "decrypt/validate failed", destlist[hostidx].name); return 0; } func = (uint8_t *)decrypted; message = decrypted; meslen = decryptlen; } else { if ((keytype != KEY_NONE) && ((header->func == KEYINFO_ACK) || (header->func == FILEINFO_ACK))) { log1(finfo->group_id, finfo->file_id, "Rejecting %s message from %08X: not encrypted", func_name(header->func), ntohl(header->src_id)); return 0; } func = (uint8_t *)&header->func; message = packet + sizeof(struct uftp_h); meslen = packetlen - sizeof(struct uftp_h); } if (*func == ABORT) { handle_abort(message, meslen, hostidx, finfo, header->src_id); return 1; } if (hostidx == -1) { if (open_group) { if (*func == REGISTER) { handle_open_register(message, meslen, finfo, receiver, header->src_id, regconf); } else if (*func == CLIENT_KEY) { handle_open_clientkey(message, meslen, finfo, receiver, header->src_id); } else { log1(finfo->group_id, finfo->file_id, "Invalid function: " "expected REGISTER or CLIENT_KEY, got %s", func_name(*func)); } } else { log1(finfo->group_id, finfo->file_id, "Host %08X not in host list", ntohl(header->src_id)); send_abort(finfo, "Not in host list", receiver, header->src_id,0,0); } } else { switch (destlist[hostidx].status) { case DEST_MUTE: if (*func == REGISTER) { handle_register(message, meslen, finfo, receiver, hostidx, regconf, open_group); } else if (*func == CLIENT_KEY) { handle_clientkey(message, meslen, finfo, receiver, hostidx); } else { log1(finfo->group_id, finfo->file_id, "Invalid function: " "expected REGISTER or CLIENT_KEY, got %s", func_name(*func)); } break; case DEST_REGISTERED: if (announce && (*func == KEYINFO_ACK)) { handle_keyinfo_ack(message, meslen, finfo, receiver, hostidx); } else if (!announce && (*func == FILEINFO_ACK)) { handle_fileinfo_ack(message, meslen, finfo, hostidx); } else if (*func == REGISTER) { handle_register(message, meslen, finfo, receiver, hostidx, regconf, open_group); } else if (*func == CLIENT_KEY) { log2(finfo->group_id, finfo->file_id, "Received CLIENT_KEY+ " "from %s", destlist[hostidx].name); } else if (!announce && (*func == COMPLETE)) { handle_complete(message, meslen, finfo, hostidx); } else { log1(finfo->group_id, finfo->file_id, "Received invalid message %s from %s", func_name(*func), destlist[hostidx].name); } break; case DEST_ACTIVE: if (*func == REGISTER) { handle_register(message, meslen, finfo, receiver, hostidx, regconf, open_group); } else if (*func == CLIENT_KEY) { log2(finfo->group_id, finfo->file_id, "Received CLIENT_KEY+ " "from %s", destlist[hostidx].name); } else if (announce && (*func == KEYINFO_ACK)) { finfo->deststate[hostidx].conf_sent = 0; handle_keyinfo_ack(message, meslen, finfo, receiver, hostidx); } else if (!announce && (*func == FILEINFO_ACK)) { handle_fileinfo_ack(message, meslen, finfo, hostidx); } else if (!announce && (*func == COMPLETE)) { handle_complete(message, meslen, finfo, hostidx); } else { log1(finfo->group_id, finfo->file_id, "Received invalid message %s from %s", func_name(*func), destlist[hostidx].name); } break; default: log1(finfo->group_id, finfo->file_id, "Received invalid message %s from %s", func_name(*func), destlist[hostidx].name); break; } } return 1; } /** * Perform the Announce/Register phase for a particular group/file * Group & encryption: ->ANNOUNCE <-REGISTER ->KEYINFO <-KEYINFO_ACK * Group & no encryption: ->ANNOUNCE <-REGISTER ->REG_CONF * Files within a group: ->FILEINFO <-FILEINFO_ACK * If client_key == 1, REGISTER is followed by CLIENT_KEY * Returns 1 if at least one client responded, 0 if none responded */ int announce_phase(struct finfo_t *finfo) { int attempt, resend, announce, regconf, keyinfo, fileinfo; int open_group, anyerror; int len, rval, rcv_status, last_pass, gotall, gotone, allreg, regdone, i; unsigned char *packet, *decrypted; struct timeval timeout, next_send, now; union sockaddr_u receiver; int grtt_set; if (finfo->file_id) { log1(finfo->group_id, finfo->file_id, "File ID: %04X Name: %s", finfo->file_id, finfo->filename); log1(finfo->group_id, finfo->file_id, " sending as: %s", finfo->destfname); switch (finfo->ftype) { case FTYPE_REG: log2(finfo->group_id, finfo->file_id, "Bytes: %s Blocks: %d Sections: %d", printll(finfo->size), finfo->blocks, finfo->sections); log3(finfo->group_id, finfo->file_id, "small section size: %d, " "big section size: %d, " "# big sections: %d", finfo->secsize_small, finfo->secsize_big, finfo->big_sections); break; case FTYPE_DIR: log2(finfo->group_id, finfo->file_id, "Empty directory"); break; case FTYPE_LINK: log2(finfo->group_id, finfo->file_id, "Symbolic link to %s", finfo->linkname); break; case FTYPE_DELETE: log2(finfo->group_id, finfo->file_id, "Delete file/directory with this name"); break; case FTYPE_FREESPACE: log2(finfo->group_id, finfo->file_id, "Free disk space query"); break; } } else { log2(finfo->group_id, finfo->file_id, "Initializing group"); } rval = 1; packet = safe_calloc(MAXMTU, 1); decrypted = safe_calloc(MAXMTU, 1); announce = (finfo->file_id == 0); regconf = (announce && (keytype == KEY_NONE)); keyinfo = (announce && (keytype != KEY_NONE)); fileinfo = (finfo->file_id != 0); open_group = (destcount == 0); for (i = 0; i < destcount; i++) { // At start of group, initialize all clients/proxies to DEST_MUTE. // At start of file, initialize proxies to DEST_ACTIVE (since they // don't respond directly to a FILEINFO) and clients to DEST_REGISTERED. if (announce) { destlist[i].status = DEST_MUTE; } else if (!client_error(i)) { if (destlist[i].clientcnt != -1) { destlist[i].status = DEST_ACTIVE; } else { destlist[i].status = DEST_REGISTERED; } destlist[i].freespace = -1; } } gettimeofday(&next_send, NULL); add_timeval_d(&next_send, 3 * grtt); resend = 1; attempt = 1; last_pass = 0; regdone = 0; grtt_set = !announce; while (attempt <= robust) { if (user_abort) break; // On the initial pass, or when the timeout trips, // send any necessary messages. if (resend) { if (keyinfo && !send_keyinfo(finfo, attempt)) { continue; } if (announce && !send_regconf(finfo, attempt, regconf)) { continue; } if (fileinfo && !send_fileinfo(finfo, attempt)) { continue; } if (announce && !send_announce(finfo, attempt, open_group)) { continue; } resend = 0; } gettimeofday(&now, NULL); if (cmptimestamp(now, next_send) >= 0) { timeout.tv_sec = 0; timeout.tv_usec = 0; } else { timeout = diff_timeval(next_send, now); } if ((rcv_status = read_packet(sock, &receiver, packet, &len, MAXMTU, &timeout)) == -1) { continue; } else if (rcv_status == 0) { attempt++; grtt_set |= recalculate_grtt(finfo, grtt_set, 0); gettimeofday(&next_send, NULL); add_timeval_d(&next_send, 3 * grtt); resend = 1; if (last_pass) break; continue; } if (!validate_packet(packet, len, finfo)) { continue; } if (!handle_announce_phase(packet, decrypted, len, &receiver, finfo, announce, open_group, regconf)) { continue; } if (!open_group) { for (i = 0, gotall = 1, allreg = 1; (i < destcount) && (gotall || allreg); i++) { if (announce) { gotall = gotall && ((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_ABORT)); allreg = allreg && ((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_REGISTERED) || (destlist[i].status == DEST_ABORT)); } else { gotall = gotall && ((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_DONE) || (client_error(i))); } } if (gotall) { // Break out right away if this is a file registration. // For group registration, do one last wait, even if // encryption is enabled since we can still send a // REG_CONF for a client behind a proxy. // Change the timeout to 1 * grtt // to allow for late registers. if (finfo->file_id != 0) break; recalculate_grtt(finfo, grtt_set, 0); gettimeofday(&next_send, NULL); add_timeval_d(&next_send, grtt); if (!last_pass) { log2(finfo->group_id, finfo->file_id, "Late registers:"); } last_pass = 1; send_regconf(finfo, attempt + 1, regconf); } else if (announce && allreg && !regdone) { // All have registered, so don't wait to send the next message resend = 1; regdone = 1; } } } recalculate_grtt(finfo, 1, 1); for (i = 0, gotone = 0, anyerror = 0; i < destcount; i++) { gotone = gotone || (((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_DONE)) && (destlist[i].clientcnt == -1)); if (destlist[i].status == DEST_REGISTERED) { if (announce) { log1(finfo->group_id, finfo->file_id, "Couldn't get KEYINFO_ACK from %s", destlist[i].name); } else { log1(finfo->group_id, finfo->file_id, "Couldn't get FILEINFO_ACK from %s", destlist[i].name); } destlist[i].status = DEST_LOST; anyerror = 1; } if ((destlist[i].status == DEST_MUTE) || (destlist[i].status == DEST_ABORT)) { anyerror = 1; } } if (anyerror && quit_on_error) { log0(finfo->group_id, finfo->file_id, "Aboring all clients"); send_abort(finfo, "A client dropped out, aborting all", &receive_dest, 0, (keytype != KEY_NONE), 0); for (i = 0; i < destcount; i++) { if (destlist[i].status == DEST_ACTIVE) { destlist[i].status = DEST_ABORT; } } rval = 0; } if (!gotone) { log0(finfo->group_id, finfo->file_id, "Announce timed out"); rval = 0; } if (open_group) { send_regconf(finfo, attempt, regconf); } if ((finfo->file_id == 0) && status_file) { for (i = 0; i < destcount; i++) { if (destlist[i].status == DEST_ACTIVE) { fprintf(status_file, "CONNECT;success;%s\n", destlist[i].name); } else { fprintf(status_file, "CONNECT;failed;%s\n", destlist[i].name); } } log0(finfo->group_id, finfo->file_id, "- Transfer -"); } free(packet); free(decrypted); return rval; } /** * Check a transfer phase message and pass to appropriate message handler, * decrypting first if necessary */ void handle_transfer_phase(unsigned char *packet,unsigned char *decrypted, int packetlen, const union sockaddr_u *receiver, struct finfo_t *finfo, int *got_naks) { struct uftp_h *header; unsigned char *message; int hostidx; unsigned int decryptlen, meslen; uint8_t *func; header = (struct uftp_h *)packet; hostidx = find_client(header->src_id); if ((keytype != KEY_NONE) && (header->func == ENCRYPTED)) { if (hostidx == -1) { log1(finfo->group_id, finfo->file_id, "Host %08X not in host list", ntohl(header->src_id)); send_abort(finfo, "Not in host list", receiver, header->src_id,0,0); return; } if (!validate_and_decrypt(packet, packetlen, &decrypted, &decryptlen, keytype, groupkey, groupsalt, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, destlist[hostidx].encinfo->pubkey, destlist[hostidx].encinfo->pubkeylen)) { log1(finfo->group_id, finfo->file_id, "Rejecting message from %s: " "decrypt/validate failed", destlist[hostidx].name); return; } func = (uint8_t *)decrypted; message = decrypted; meslen = decryptlen; } else { if ((keytype != KEY_NONE) && ( (header->func == STATUS) || (header->func == COMPLETE) || (header->func == ABORT))) { log1(finfo->group_id, finfo->file_id, "Rejecting %s message from %08X: not encrypted", func_name(header->func), ntohl(header->src_id)); return; } func = (uint8_t *)&header->func; message = packet + sizeof(struct uftp_h); meslen = packetlen - sizeof(struct uftp_h); } if (*func == ABORT) { handle_abort(message, meslen, hostidx, finfo, header->src_id); } else if (hostidx == -1) { log1(finfo->group_id, finfo->file_id, "Host %08X not in host list", ntohl(header->src_id)); send_abort(finfo, "Not in host list", receiver, header->src_id, 0, 0); } else { switch (destlist[hostidx].status) { case DEST_ACTIVE: case DEST_ACTIVE_NAK: if (*func == STATUS) { handle_status(message, meslen, finfo, hostidx, got_naks); } else if (*func == COMPLETE) { handle_complete(message, meslen, finfo, hostidx); } else if (*func == CC_ACK) { handle_cc_ack(message, meslen, finfo, hostidx); } else { log1(finfo->group_id, finfo->file_id, "Received invalid message %s from %s", func_name(*func), destlist[hostidx].name); } break; case DEST_DONE: if (*func == COMPLETE) { handle_complete(message, meslen, finfo, hostidx); } else { log1(finfo->group_id, finfo->file_id, "Received invalid message %s from %s", func_name(*func), destlist[hostidx].name); } break; } } return; } /** * Adjust the sending rate based on percentage of NAKs received to blocks sent * TODO: this has to change to accomodate lost percentage as received from CC */ void adjust_rate(int naks, int blocks) { int percentage, i; percentage = naks * 100 / blocks; for (i = 0; i < cc_count; i++) { if (cc_list[i].percentage >= percentage) { rate = (int)(rate * cc_list[i].scaling_factor); if (rate > max_rate) rate = max_rate; packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); log2(0, 0, "Updated rate: %d Kbps (%d KB/s)", rate * 8 / 1024, rate / 1024); log2(0, 0, "Wait between packets: %d us", packet_wait); break; } } } /** * Seeks to a particular block in a file * Returns 1 on success, 0 on error */ int seek_block(const struct finfo_t *finfo, int file, int block, f_offset_t *offset) { f_offset_t new_offset; if ((new_offset = lseek_func(file, ((f_offset_t)block * blocksize) - *offset, SEEK_CUR)) == -1) { syserror(finfo->group_id, finfo->file_id, "lseek failed for file"); return 0; } if (new_offset != (f_offset_t)block * blocksize) { log0(finfo->group_id, finfo->file_id, "block %d: offset is %s", block, printll(new_offset)); log0(finfo->group_id, finfo->file_id, " should be %s", printll((f_offset_t)block * blocksize)); if ((new_offset = lseek_func(file, ((f_offset_t)block * blocksize) - (new_offset), SEEK_CUR)) == -1) { syserror(finfo->group_id, finfo->file_id, "lseek failed for file"); return 0; } } *offset = new_offset; return 1; } /* TODO: Have the server echo back any received NAKs, and have the clients suppress their own NAKs based on that. */ /** * Variables shared between the sending and receiving threads */ mux_t mux_main; struct cc_queue_item { unsigned char *data; int len; }; #define CC_QUEUE_LEN 100 static struct cc_queue_item cc_queue[CC_QUEUE_LEN]; static int cc_queue_start, cc_queue_end, end_of_pass; static int file_done_flag, rewind_flag, rewind_pending_flag, max_time_timeout; int rate_change; uint32_t current_position; uint16_t cc_seq; uint32_t cc_rate; double adv_grtt; // TODO: not shared between threads, move? int slowstart, clr, clr_drop, new_rate; struct timeval last_clr_time; static int file; /** * Read item from cc_queue. The calling thread must be holding the lock. * Returns the head of the list, or NULL if the list is empty. */ void get_cc_queue(unsigned char **item, int *len) { if ((cc_queue_start == cc_queue_end) && (cc_queue[cc_queue_end].data == NULL)) { *item = NULL; *len = 0; return; } *item = cc_queue[cc_queue_start].data; *len = cc_queue[cc_queue_start].len; cc_queue[cc_queue_start].data = NULL; cc_queue[cc_queue_start++].len = 0; if (cc_queue_start == CC_QUEUE_LEN) { cc_queue_start = 0; } } /** * Put item on cc_queue. The calling thread must be holding the lock. * Returns true if the item was added, false if the list is full. */ int put_cc_queue(const struct finfo_t *finfo, unsigned char *item, int len) { if ((cc_queue_start == cc_queue_end) && (cc_queue[cc_queue_end].data != NULL)) { log1(finfo->group_id, finfo->file_id, "cc_queue full"); return 0; } cc_queue[cc_queue_end].data = item; cc_queue[cc_queue_end++].len = len; if (cc_queue_end == CC_QUEUE_LEN) { cc_queue_end = 0; } return 1; } /** * Thread for sending all packets during the transfer phase */ THREAD_FUNC transfer_send_thread(void *infop) { unsigned char *packet, *encpacket, *data, *cc_body; struct uftp_h *header; struct fileseg_h *fileseg; struct tfmcc_data_info_he *tfmcc; int numbytes, attempt, l_file_done_flag, current_nak, cc_len, done_sent; double l_adv_grtt; uint16_t l_cc_seq, pass, section, last_section; uint32_t l_cc_rate, block; int l_packet_wait, l_rate_change, max_time; struct timeval last_sent, current_sent, now, start_time; int64_t overage, tdiff; f_offset_t offset, curr_offset; struct finfo_t *finfo; finfo = infop; // Not mutexed, but should be OK when the thread first starts l_cc_seq = cc_seq; l_cc_rate = cc_rate; l_packet_wait = packet_wait; l_adv_grtt = adv_grtt; l_rate_change = 0; packet = safe_calloc(MAXMTU, 1); encpacket = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)packet; fileseg = (struct fileseg_h *)(packet + sizeof(struct uftp_h)); if (cc_type == CC_TFMCC) { tfmcc = (struct tfmcc_data_info_he *)((unsigned char *)fileseg + sizeof(struct fileseg_h)); tfmcc->exttype = EXT_TFMCC_DATA_INFO; tfmcc->extlen = sizeof(struct tfmcc_data_info_he) / 4; fileseg->hlen = (sizeof(struct fileseg_h) + sizeof(struct tfmcc_data_info_he)) / 4; } else { tfmcc = NULL; fileseg->hlen = sizeof(struct fileseg_h) / 4; } data = (unsigned char *)fileseg + (fileseg->hlen * 4); set_uftp_header(header, FILESEG, finfo->group_id, finfo->group_inst, l_adv_grtt, destcount); gettimeofday(&start_time, NULL); gettimeofday(&last_sent, NULL); overage = 0; offset = 0; lseek_func(file, 0, SEEK_SET); fileseg->func = FILESEG; fileseg->file_id = htons(finfo->file_id); // If all clients received this file partially on a prior attempt, // set the block counter at the end so we start by sending a DONE if (finfo->partial) { block = finfo->blocks; } else { block = 0; } done_sent = 0; current_nak = 1; attempt = 1; l_file_done_flag = 0; if ((cc_type == CC_NONE) && (rate != -1) && (txweight != 0)) { max_time = (int)(0 + floor(((double)txweight / 100) * ((double)finfo->size / rate))); log2(finfo->group_id, finfo->file_id, "Maximum file transfer time: %d seconds", max_time); } else { max_time = 0; } pass = 1; section = 0; last_section = (uint16_t)-1; log2(finfo->group_id, finfo->file_id, "Sending file"); log2(finfo->group_id, finfo->file_id, "Starting pass %u", pass); do { if (block < finfo->blocks) { if (current_nak) { log5(finfo->group_id, finfo->file_id, "Sending %d, wait=%d", block, l_packet_wait); attempt = 1; // TODO: try to avoid seek on consecutive packets? curr_offset = offset; if (!seek_block(finfo, file, block, &curr_offset)) { continue; } offset = curr_offset; if ((numbytes = read(file, data, blocksize)) == -1) { syserror(finfo->group_id, finfo->file_id, "read failed"); continue; } offset += numbytes; // Keep track of how long we really slept compared to how // long we expected to sleep. If we went over, subtract the // time over from the next sleep time. This way we maintain // the proper average sleep time. if (l_packet_wait > overage) { usleep(l_packet_wait - (int32_t)overage); } gettimeofday(¤t_sent, NULL); tdiff = diff_usec(current_sent, last_sent); if (l_packet_wait) overage += tdiff - l_packet_wait; last_sent = current_sent; if (log_level >= 5) { clog5(finfo->group_id, finfo->file_id, "tdiff=%s, ", printll(tdiff)); slog5("overage=%s", printll(overage)); } // When rate changes significantly, clear the overage counter if (l_rate_change) { overage = 0; } header->grtt = quantize_grtt(l_adv_grtt); if (cc_type == CC_TFMCC) { tfmcc->send_rate = htons(quantize_rate(rate)); tfmcc->cc_seq = htons(l_cc_seq); tfmcc->cc_rate = htons(quantize_rate(l_cc_rate)); } if (block >= finfo->big_sections * finfo->secsize_big) { fileseg->section = htons(((block - (finfo->big_sections * finfo->secsize_big)) / finfo->secsize_small) + finfo->big_sections); fileseg->sec_block = htons((block - (finfo->big_sections * finfo->secsize_big)) % finfo->secsize_small); } else { fileseg->section = htons(block / finfo->secsize_big); fileseg->sec_block = htons(block % finfo->secsize_big); } section = ntohs(fileseg->section); if (last_section != section) { log2(finfo->group_id, finfo->file_id, "Sending section %u", section); last_section = section; } send_data(finfo, packet, numbytes, encpacket); done_sent = 0; } } else { gettimeofday(&now, NULL); if (!done_sent || (diff_usec(now, last_sent) > (3 * l_adv_grtt * 1000000))) { if (attempt < robust) { if (!send_done(finfo, attempt, finfo->sections ? finfo->sections - 1 : 0, l_adv_grtt)) { log2(finfo->group_id, finfo->file_id, "Error sending DONE"); } } attempt++; gettimeofday(&last_sent, NULL); done_sent = 1; } usleep(l_packet_wait); overage = 0; } // Access anything used by both threads under one mutex all at once if (mux_lock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to lock mutex in transfer_send_thread"); continue; } if (max_time) { gettimeofday(&now, NULL); if (diff_sec(now, start_time) > max_time) { log1(finfo->group_id, finfo->file_id, "Maximum file transfer time exceeded"); max_time_timeout = 1; file_done_flag = 1; } } if (block < finfo->blocks) { finfo->naklist[block] = 0; block++; } else { end_of_pass = 1; } if (rewind_flag) { log2(finfo->group_id, finfo->file_id, "Starting pass %u", ++pass); block = 0; rewind_flag = 0; end_of_pass = 0; last_section = (uint16_t)-1; } if (block < finfo->blocks) { current_nak = finfo->naklist[block]; } else { current_nak = 0; } current_position = block; if ((attempt > robust) && !rewind_pending_flag) { log1(finfo->group_id, finfo->file_id, "Sending thread timed out"); file_done_flag = 1; } l_cc_seq = cc_seq; l_cc_rate = cc_rate; l_adv_grtt = adv_grtt; l_packet_wait = packet_wait; l_file_done_flag = file_done_flag; l_rate_change = rate_change; rate_change = 0; get_cc_queue(&cc_body, &cc_len); if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex in transfer_send_thread"); // TODO: if we can't unlock, kill the thread and fail the file continue; } if (cc_body) { send_cong_ctrl(finfo, l_adv_grtt, l_cc_seq, l_cc_rate, cc_body, cc_len); free(cc_body); cc_body = NULL; } } while (!l_file_done_flag); free(packet); free(encpacket); THREAD_RETURN; } /** * Thread for receiving all packets during the transfer phase * Called directly from transfer_phase */ void transfer_receive_thread(struct finfo_t *finfo) { unsigned char *packet, *decrypted, *cc_body; union sockaddr_u receiver; struct timeval now, timeout, rewind_time, fb_end, next_cc; struct timeval min_tstamp; int l_file_done_flag, got_naks, cc_len; int alldone, found_error, i, len, rcv_status, found_timeout; int do_rewind, do_nextcc, do_halfrate; int64_t last_clr; double l_adv_grtt; packet = safe_calloc(MAXMTU, 1); decrypted = safe_calloc(MAXMTU, 1); alldone = 0; rewind_time.tv_sec = 0; rewind_time.tv_usec = 0; got_naks = 0; next_cc.tv_sec = 0; next_cc.tv_usec = 0; // Not mutexed, but should be OK when the thread first starts l_adv_grtt = adv_grtt; log4(finfo->group_id, finfo->file_id, "adv_grtt=%.3f", l_adv_grtt); if (cc_type == CC_TFMCC) { // Start a feedback round gettimeofday(&last_clr_time, NULL); gettimeofday(&fb_end, NULL); add_timeval_d(&fb_end, 6 * l_adv_grtt); } else { fb_end.tv_sec = 0; fb_end.tv_usec = 0; } l_file_done_flag = 0; do { do_rewind = 0; do_nextcc = 0; do_halfrate = 0; gettimeofday(&now, NULL); if ((cc_type == CC_TFMCC) && (cmptimestamp(now, next_cc) > 0)) { create_cc_list(&cc_body, &cc_len); if (!put_cc_queue(finfo, cc_body, cc_len)) { log1(finfo->group_id, finfo->file_id, "Couldn't queue up CONG_CTRL: list full!"); free(cc_body); } else { gettimeofday(&next_cc, NULL); add_timeval_d(&next_cc, 1 * l_adv_grtt); } log3(finfo->group_id, finfo->file_id, "CONG_CTRL queued"); // TODO: bypass this check if selected less that 10 RTT ago? if (diff_usec(now, last_clr_time) > 1000000 * 4 * l_adv_grtt) { do_halfrate = 1; log5(finfo->group_id, finfo->file_id, "Halfing rate"); } if (diff_usec(now, last_clr_time) > 1000000 * robust * l_adv_grtt) { clr = -1; clr_drop = 1; log5(finfo->group_id, finfo->file_id, "Lost clr"); } } if ((rewind_time.tv_sec) && (cmptimestamp(now, rewind_time) >= 0)) { do_rewind = 1; } if ((fb_end.tv_sec) && (cmptimestamp(now, fb_end) >= 0)) { do_nextcc = 1; } if (do_rewind || do_nextcc || do_halfrate) { if (mux_lock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to lock mutex " "in transfer_receive_thread for rewind / feedback"); continue; } } if (do_rewind) { log3(finfo->group_id, finfo->file_id, "Rewind timer tripped"); rewind_flag = 1; rewind_time.tv_sec = 0; rewind_time.tv_usec = 0; got_naks = 0; for (i = 0; i < destcount; i++) { if (destlist[i].status == DEST_ACTIVE_NAK) { destlist[i].status = DEST_ACTIVE; } } } if (do_halfrate) { rate /= 2; if (rate < datapacketsize / grtt) { slowstart = 1; rate = (int)(datapacketsize / grtt); } packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); rate_change = 1; last_clr = diff_usec(now, last_clr_time); if ((last_clr > 1000000 * 2 * robust * l_adv_grtt) && (last_clr > 1000000 * 5)) { // No new CLR chosen in 2*robust RTTs (or 5 seconds) since the // prior one dropped, meaning no feedback from anyone, so quit log2(finfo->group_id, finfo->file_id, "No feedback in %d GRTTs", 2 * robust); file_done_flag = 1; } } if (do_nextcc) { // TODO: Handle extended feedback round if (clr_drop) { clr_drop = 0; if (new_rate) { rate = new_rate; } packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); rate_change = 1; } recalculate_grtt(finfo, 1, 1); adv_grtt = (double)datapacketsize / rate; if (adv_grtt < grtt) { adv_grtt = grtt; } l_adv_grtt = adv_grtt; log5(finfo->group_id, finfo->file_id, "adv_grtt=%.3f", l_adv_grtt); if (rate < datapacketsize / grtt) { slowstart = 1; rate = (int)(datapacketsize / grtt); packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); rate_change = 1; } gettimeofday(&fb_end, NULL); add_timeval_d(&fb_end, 6 * l_adv_grtt); cc_rate = 0xFFFFFFFF; cc_seq++; log4(finfo->group_id, finfo->file_id, "Starting feedback round %d", cc_seq); } if (do_rewind || do_nextcc || do_halfrate) { if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex " "in transfer_receive_thread for rewind / feedback"); continue; } } found_timeout = 0; if (rewind_time.tv_sec) { min_tstamp = rewind_time; found_timeout = 1; } if (fb_end.tv_sec) { if (!found_timeout || (cmptimestamp(fb_end, min_tstamp) < 0)) { min_tstamp = fb_end; found_timeout = 1; } } if (cc_type == CC_TFMCC) { if (!found_timeout || (cmptimestamp(next_cc, min_tstamp) < 0)) { min_tstamp = next_cc; found_timeout = 1; } } if (found_timeout) { timeout = diff_timeval(min_tstamp, now); } else { timeout.tv_sec = 0; timeout.tv_usec = 0; add_timeval_d(&timeout, 1 * l_adv_grtt); } if ((rcv_status = read_packet(sock, &receiver, packet, &len, MAXMTU, &timeout)) == -1) { continue; } else if (rcv_status == 0) { // Timeouts get handled at the top of the loop } else if (validate_packet(packet, len, finfo)) { handle_transfer_phase(packet, decrypted, len, &receiver, finfo, &got_naks); for (i = 0, alldone = 1; (i < destcount) && alldone; i++) { alldone = alldone && ((destlist[i].status == DEST_DONE) || (client_error(i)) || (destlist[i].clientcnt != -1)); } } if (mux_lock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to lock mutex in transfer_receive_thread"); continue; } if (alldone || user_abort) { file_done_flag = 1; } else if (got_naks && end_of_pass && (rewind_time.tv_sec == 0)) { gettimeofday(&rewind_time, NULL); add_timeval_d(&rewind_time, 3 * l_adv_grtt); log3(finfo->group_id, finfo->file_id, "Starting rewind timer"); } l_file_done_flag = file_done_flag; l_adv_grtt = adv_grtt; log5(finfo->group_id, finfo->file_id, "adv_grtt=%.3f", l_adv_grtt); rewind_pending_flag = (rewind_time.tv_sec != 0); if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex in transfer_receive_thread"); continue; } } while (!l_file_done_flag); found_error = 0; if (user_abort || max_time_timeout) { log0(finfo->group_id, finfo->file_id, "Aboring all clients"); if (user_abort) { send_abort(finfo, "Server quit, aborting all", &receive_dest, 0, (keytype != KEY_NONE), 0); } else if (max_time_timeout) { send_abort(finfo, "Max file transfer time exceeded", &receive_dest, 0, (keytype != KEY_NONE), 0); } for (i = 0; i < destcount; i++) { if ((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_ACTIVE_NAK)) { destlist[i].status = DEST_ABORT; } } } else if (!alldone) { for (i = 0; i < destcount; i++) { if ((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_ACTIVE_NAK)) { log1(finfo->group_id, finfo->file_id, "No response from %s", destlist[i].name); destlist[i].status = DEST_LOST; if (quit_on_error && !found_error) { found_error = 1; log0(finfo->group_id,finfo->file_id, "Aboring all clients"); send_abort(finfo, "A client dropped out, aborting all", &receive_dest, 0, (keytype != KEY_NONE), 0); } } } } free(packet); free(decrypted); } /** * Performs the Transfer phase for a particular file. * It sits in a loop to do all reads, and it starts a thread to do all writes * Returns 1 if at least one client finished, 0 if all are dropped or aborted */ int transfer_phase(struct finfo_t *finfo) { thread_t tid; int alldone, i; double tmp_rtt; uint32_t nak; char path[MAXPATHNAME]; struct timeval start_time; // First check to see if all clients are already done for this file. // This can happen on a restart when the file finished on the // last attempt and responded to the FILEINFO with a COMPLETE for (i = 0, alldone = 1; (i < destcount) && alldone; i++) { alldone = alldone && ((destlist[i].status == DEST_DONE) || (client_error(i)) || (destlist[i].clientcnt != -1)); } if (alldone) { gettimeofday(&start_time, NULL); print_status(finfo, start_time); return 1; } // TODO: if not a regular file, error any clients that didn't // respond to the FILEINFO with a COMPLETE if (finfo->ftype == FTYPE_REG) { snprintf(path, sizeof(path), "%s%c%s", finfo->basedir, PATH_SEP, finfo->filename); // Open the file now so we don't start the sending thread if it fails if ((file = open(path, OPENREAD, 0)) == -1) { syserror(finfo->group_id, finfo->file_id, "Error opening file"); return 1; } } else { // At end of group, all non-errored client are DEST_DONE from the // last file, so reset them to DEST_ACTIVE to get the final COMPLETE. for (i = 0; i < destcount; i++) { if (!client_error(i)) { destlist[i].status = DEST_ACTIVE; destlist[i].max_nak_exceed = 0; } } } gettimeofday(&start_time, NULL); max_time_timeout = 0; file_done_flag = 0; rewind_flag = 0; cc_queue_start = 0; cc_queue_end = 0; rate_change = 0; end_of_pass = 0; if (cc_type == CC_TFMCC) { // Initialize rate to 1 packet per GRTT rate = (int32_t)(((double)datapacketsize / grtt)); packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); adv_grtt = (double)datapacketsize / rate; if (adv_grtt < grtt) { adv_grtt = grtt; } cc_seq = 0; cc_rate = 0xFFFFFFFF; slowstart = 1; // Pick an initial CLR based on who has the highest RTT for (tmp_rtt = 9999, clr = -1, i = 0; i < destcount; i++) { if ((destlist[i].rtt < tmp_rtt) && (destlist[i].clientcnt == -1)) { clr = i; tmp_rtt = destlist[i].rtt; } } } else { adv_grtt = grtt; } for (nak = 0; nak < finfo->blocks; nak++) { if (finfo->partial) { finfo->naklist[nak] = 0; } else { finfo->naklist[nak] = 1; } } if (mux_create(mux_main)) { syserror(finfo->group_id, finfo->file_id, "Failed to create mutex"); if (finfo->ftype == FTYPE_REG) { close(file); } return 1; } use_log_mux = 1; if (start_thread(tid, transfer_send_thread, finfo) != 0) { syserror(finfo->group_id, finfo->file_id, "Failed to create sender thread"); if (finfo->ftype == FTYPE_REG) { close(file); } mux_destroy(mux_main); return 1; } transfer_receive_thread(finfo); if (join_thread(tid) != 0) { syserror(finfo->group_id,finfo->file_id,"Failed to join sender thread"); } else { destroy_thread(tid); } use_log_mux = 0; if (finfo->ftype == FTYPE_REG) { close(file); } mux_destroy(mux_main); print_status(finfo, start_time); if (user_abort) { return 0; } for (i = 0; i < destcount; i++) { if (quit_on_error) { // Check to see that all finished if ((destlist[i].status != DEST_DONE) && (destlist[i].clientcnt == -1)) { return 0; } } else { // Check to see if at least one finished if (destlist[i].status == DEST_DONE) { return 1; } } } if (quit_on_error) { return 1; } else { return 0; } } /** * Check a completion phase message and pass to appropriate message handler, * decrypting first if necessary */ void handle_completion_phase(unsigned char *packet, unsigned char *decrypted, int packetlen, const union sockaddr_u *receiver, struct finfo_t *finfo) { struct uftp_h *header; unsigned char *message; int hostidx; unsigned int decryptlen, meslen; uint8_t *func; header = (struct uftp_h *)packet; hostidx = find_client(header->src_id); if ((keytype != KEY_NONE) && (header->func == ENCRYPTED)) { if (hostidx == -1) { log1(finfo->group_id, finfo->file_id, "Host %08X not in host list", ntohl(header->src_id)); send_abort(finfo, "Not in host list", receiver, header->src_id,0,0); return; } if (!validate_and_decrypt(packet, packetlen, &decrypted, &decryptlen, keytype, groupkey, groupsalt, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, destlist[hostidx].encinfo->pubkey, destlist[hostidx].encinfo->pubkeylen)) { log1(finfo->group_id, finfo->file_id, "Rejecting message from %s: " "decrypt/validate failed", destlist[hostidx].name); return; } func = (uint8_t *)decrypted; message = decrypted; meslen = decryptlen; } else { if ((keytype != KEY_NONE) && ( (header->func == STATUS) || (header->func == COMPLETE) || (header->func == ABORT))) { log1(finfo->group_id, finfo->file_id, "Rejecting %s message from %08X: not encrypted", func_name(header->func), ntohl(header->src_id)); return; } func = (uint8_t *)&header->func; message = packet + sizeof(struct uftp_h); meslen = packetlen - sizeof(struct uftp_h); } if (*func == ABORT) { handle_abort(message, meslen, hostidx, finfo, header->src_id); } else if (hostidx == -1) { log1(finfo->group_id, finfo->file_id, "Host %08X not in host list", ntohl(header->src_id)); send_abort(finfo, "Not in host list", receiver, header->src_id, 0, 0); } else if (*func == COMPLETE) { handle_complete(message, meslen, finfo, hostidx); } else { log1(finfo->group_id, finfo->file_id, "Received invalid message %s from %s", func_name(*func), destlist[hostidx].name); } return; } /** * Performs the Completion/Confirmaton phase at the end of a group */ void completion_phase(struct finfo_t *finfo) { unsigned char *packet, *decrypted; struct timeval timeout, next_send, now, start_time; union sockaddr_u receiver; int resend, attempt, last_pass, alldone; int rcv_status, len, i; packet = safe_calloc(MAXMTU, 1); decrypted = safe_calloc(MAXMTU, 1); // At end of group, all non-errored client are DEST_DONE from the // last file, so reset them to DEST_ACTIVE to get the final COMPLETE. for (i = 0; i < destcount; i++) { if (!client_error(i)) { destlist[i].status = DEST_ACTIVE; } } log2(finfo->group_id, finfo->file_id, "Finishing group"); gettimeofday(&start_time, NULL); gettimeofday(&next_send, NULL); add_timeval_d(&next_send, 3 * grtt); resend = 1; attempt = 1; last_pass = 0; while (attempt <= robust) { if (user_abort) break; if (resend) { if (!send_doneconf(finfo, attempt)) { continue; } if (!send_done(finfo, attempt, 0, grtt)) { continue; } resend = 0; } gettimeofday(&now, NULL); if (cmptimestamp(now, next_send) >= 0) { timeout.tv_sec = 0; timeout.tv_usec = 0; } else { timeout = diff_timeval(next_send, now); } if ((rcv_status = read_packet(sock, &receiver, packet, &len, MAXMTU, &timeout)) == -1) { continue; } else if (rcv_status == 0) { attempt++; recalculate_grtt(finfo, 1, 0); gettimeofday(&next_send, NULL); add_timeval_d(&next_send, 3 * grtt); resend = 1; if (last_pass) break; continue; } if (!validate_packet(packet, len, finfo)) { continue; } handle_completion_phase(packet, decrypted, len, &receiver, finfo); for (i = 0, alldone = 1; (i < destcount) && alldone; i++) { alldone = alldone && ((destlist[i].status == DEST_DONE) || (client_error(i)) || (destlist[i].clientcnt != -1)); } if (alldone) { // Change the timeout to 1 * grtt // to allow for late completions recalculate_grtt(finfo, 1, 0); gettimeofday(&next_send, NULL); add_timeval_d(&next_send, grtt); if (!last_pass) { log2(finfo->group_id, finfo->file_id, "Late completions:"); } last_pass = 1; send_doneconf(finfo, attempt + 1); } } for (i = 0; i < destcount; i++) { if (destlist[i].status == DEST_ACTIVE) { log1(finfo->group_id, finfo->file_id, "Couldn't get COMPLETE " "for group from %s", destlist[i].name); destlist[i].status = DEST_LOST; } } send_doneconf(finfo, attempt + 1); print_status(finfo, start_time); free(packet); free(decrypted); } uftp-4.1.5/server.h0000644000076400007640000002224312176066071013173 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_H #define _SERVER_H #include "uftp_common.h" #include "encryption.h" #define MAXDEST 100000 #define MAXFILES 10000 #define MAXEXCLUDE 500 #define MAXCC 20 #define CLIENT_RTT_MIN 0.001 /** * Values for deststate_t.status */ enum client_status { DEST_MUTE = -4, /// Expected client that hasn't registered DEST_LOST = -3, /// Client that timed out responding to a DONE DEST_ABORT = -2, /// An ABORT was either sent to or received from DEST_REGISTERED = -1, /// Registered but haven't received INFO_ACK DEST_ACTIVE = 0, /// Ready to receive data DEST_ACTIVE_NAK = 1, /// Ready to receive data, sent back NAKs DEST_DONE = 2 /// Client finished successfully, sent COMPLETE }; /** * Destination state for a particular destination and file */ struct deststate_t { int conf_sent; /// False if REG_CONF or DONE_CONF needs to be sent struct timeval time; /// Time that this client finished }; /** * File info struct */ struct finfo_t { int ftype; /// File type (regular, directory, symlink) const char *basedir; /// Base pathname of file in filesystem const char *filename; /// Local name of file (may include path) const char *linkname; /// For symbolic links, the text of the link const char *destfname; /// Transmitted name of file (may include path) f_offset_t size; /// File size in bytes int32_t tstamp; /// Timestamp of file uint32_t blocks; /// Total blocks in file uint16_t sections; /// Total sections in file uint16_t big_sections; /// Number of larger sized sections uint32_t secsize_small, secsize_big; /// Size of sections uint32_t group_id; /// Group ID uint8_t group_inst; /// Group instance ID (restart number) uint16_t file_id; /// File ID char *naklist; /// Aggregate NAK list int partial; /// True if all clients partially received last run struct deststate_t *deststate; /// Status array for each client }; /** * Encryption info for a particilar destination * Only allocated when encryption is enabled and only for proxies and clients * talking directly to the server. Clients communicating through one or * more proxies maintain encryption state with the proxy, not the server. */ struct encinfo_t { union key_t pubkey; /// The client's RSA or ECDSA public key int pubkeylen; /// The client's public key length in bytes union key_t dhkey; /// The client's ECDH public key uint8_t verifydata[PUBKEY_LEN]; /// The verify data from a CLIENT_KEY uint16_t verifylen; /// The length of verifydata in bytes uint8_t rand2[RAND_LEN]; /// Client's random number uint8_t premaster[MASTER_LEN]; /// Premaster secret sent by client int premaster_len; /// Length of premaster secret uint8_t master[MASTER_LEN]; /// Master key for client uint8_t hmackey[HMAC_LEN]; /// HMAC key for client uint8_t key[MAXKEY]; /// Symmetric encryption key for client uint8_t salt[MAXIV]; /// Salt for block cypher IV for client }; /** * Destination info */ struct destinfo_t { char name[DESTNAME_LEN]; /// Hostname of client uint32_t id; /// UID of client (network byte order) int proxyidx; /// Index of the proxy serving this client int clientcnt; /// The number of clients a proxy serves uint32_t *clients; /// Indexes of clients served by this proxy int64_t freespace; /// Free disk space reported by client int8_t status; /// Specified by a client_status value int8_t comp_status; /// Completion status as given by COMPLETE int8_t registered; /// True if we received a REGISTER int8_t verified; /// True if we have a verified CLIENT_KEY int num_copy; /// Number of copied files in sync mode int num_skip; /// Number of skipped files in sync mode int num_overwrite; /// Number of overwritten files in sync mode double total_time; /// Total elasped sending time in sync mode f_offset_t total_size; /// Total number of bytes sent in sync mode double rtt; /// RTT to this client int8_t rtt_measured; /// True if RTT measured this round int8_t rtt_sent; /// True if RTT sent in a CONG_CTRL int8_t max_nak_exceed; /// How often client exceeded max naks int8_t has_fingerprint; /// True if we have client's key fingerprint uint8_t keyfingerprint[HMAC_LEN]; /// Fingerprint of RSA key struct encinfo_t *encinfo; /// If encryption enabled, encryption info }; /** * Header of server restart file. * Followed in the file by filecount file names of MAXPATHNAME length, * followed by an arbitrary number of server_restart_host_t entries. */ struct server_restart_t { uint32_t group_id; /// Group ID of failed transfer uint8_t group_inst; /// Group instance ID of failed transfer int filecount; /// Number of files specified on cmdline }; /** * Server restart file entry for a particular host. */ struct server_restart_host_t { char name[DESTNAME_LEN]; /// Hostname of client uint32_t id; /// UID of client (network byte order) int has_fingerprint; /// True if we have client's key fingerprint uint8_t keyfingerprint[HMAC_LEN]; /// Fingerprint of RSA key int is_proxy; /// True if this is a proxy }; /** * Congestion control configuration entrie. */ struct cc_config_t { double scaling_factor; /// The given scaling factor int percentage; /// NAK percentage for the scaling factor }; /** * Global command line values and sockets */ extern SOCKET sock; extern union sockaddr_u listen_dest, receive_dest; extern int max_rate, rate, rcvbuf, packet_wait, txweight, max_nak_pct; extern int client_auth, quit_on_error, dscp, follow_links, max_nak_cnt; extern int save_fail, restart_groupid, restart_groupinst; extern int sync_mode, sync_preview, dest_is_dir, cc_type, user_abort; extern unsigned int ttl; extern char port[PORTNAME_LEN], srcport[PORTNAME_LEN]; extern char pub_multi[INET6_ADDRSTRLEN], priv_multi[INET6_ADDRSTRLEN]; extern char keyfile[MAXPATHNAME], cc_config[MAXPATHNAME]; extern char filelist[MAXFILES][MAXPATHNAME], exclude[MAXEXCLUDE][MAXPATHNAME]; extern char basedir[MAXDIR][MAXDIRNAME], destfname[MAXPATHNAME]; extern char statusfilename[MAXPATHNAME]; extern FILE *status_file; extern struct iflist ifl[MAX_INTERFACES]; extern int keytype, hashtype, sigtype, keyextype, newkeylen, sys_keys; extern int blocksize, datapacketsize; extern int ifl_len, destcount, filecount, excludecount, basedircount, cc_count; extern struct cc_config_t cc_list[MAXCC]; extern struct iflist out_if; extern struct destinfo_t destlist[MAXDEST]; extern int robust; extern double grtt, min_grtt, max_grtt; extern uint16_t send_seq; extern uint32_t server_id; /** * Encryption variables */ extern union key_t privkey, dhkey; extern unsigned char rand1[RAND_LEN], groupmaster[MASTER_LEN]; extern uint8_t groupsalt[MAXIV], groupkey[MAXKEY], grouphmackey[HMAC_LEN]; extern uint64_t ivctr; extern int ivlen, keylen, hmaclen, privkeylen; extern uint8_t ecdh_curve, ecdsa_curve; /** * Variables shared between the sending and receiving threads * Defined in server_phase.c */ extern mux_t mux_main; extern int rate_change; extern uint32_t current_position; extern uint32_t rewind_to; extern uint16_t cc_seq; extern uint32_t cc_rate; extern double adv_grtt; extern int slowstart, clr, clr_drop, new_rate; extern struct timeval last_clr_time; #endif // _SERVER_H uftp-4.1.5/client_init.h0000644000076400007640000000270712250244021014153 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_INIT_H #define _CLIENT_INIT_H void pre_initialize(void); void initialize(void); #endif // _CLIENT_INIT_H uftp-4.1.5/heartbeat_send.c0000644000076400007640000001671512250244021014621 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #ifdef WINDOWS #include #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #include #endif #include "uftp_common.h" #include "heartbeat_send.h" /** * Process an HB_RESP message */ void handle_hb_response(SOCKET s, const union sockaddr_u *src, const unsigned char *message, unsigned meslen, union sockaddr_u hb_hosts[], int num_hosts, union key_t privkey, int keytype, uint32_t uid) { const struct hb_resp_h *hbresp; char addrname[INET6_ADDRSTRLEN]; int hostidx, rval; hbresp = (const struct hb_resp_h *)message; if (meslen < (hbresp->hlen * 4U) || ((hbresp->hlen * 4U) < sizeof(struct hb_resp_h))) { log2(0, 0, "Rejecting HB_RESP: invalid message size"); return; } if ((rval = getnameinfo((const struct sockaddr *)src, sizeof(union sockaddr_u), addrname, sizeof(addrname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Received HB_RESP from %s", addrname); if (hbresp->authenticated == HB_AUTH_CHALLENGE) { log1(0, 0, "Heartbeat authentication required"); for (hostidx = 0; hostidx < num_hosts; hostidx++) { if (addr_equal(src, &hb_hosts[hostidx])) { send_auth_hb_request(s, &hb_hosts[hostidx], ntohl(hbresp->nonce), privkey, keytype, uid); break; } } } else if (hbresp->authenticated == HB_AUTH_FAILED) { log1(0, 0, "Heartbeat authentication failed"); } else if (hbresp->authenticated == HB_AUTH_OK) { log2(0, 0, "Heartbeat authentication successful"); } } /** * Sends an authenticated HB_REQ message to the given host. */ void send_auth_hb_request(SOCKET s, union sockaddr_u *hbhost, uint32_t nonce, union key_t privkey, int keytype, uint32_t uid) { unsigned char *packet, *keyblob, *sig; struct uftp_h *header; struct hb_req_h *hbreq; uint32_t n_nonce; unsigned int meslen, siglen, rval; uint16_t bloblen; char addrname[INET6_ADDRSTRLEN], portstr[PORTNAME_LEN]; packet = safe_calloc(sizeof(struct uftp_h) + sizeof(struct hb_req_h) + (PUBKEY_LEN * 2) , 1); header = (struct uftp_h *)packet; hbreq = (struct hb_req_h *)(packet + sizeof(struct uftp_h)); keyblob = (unsigned char *)hbreq + sizeof(struct hb_req_h); header->version = UFTP_VER_NUM; header->func = HB_REQ; header->src_id = uid; hbreq->func = HB_REQ; n_nonce = htonl(nonce); hbreq->nonce = n_nonce; if (keytype == KEYBLOB_RSA) { if (!export_RSA_key(privkey.rsa, keyblob, &bloblen)) { log0(0, 0, "Error exporting public key"); free(packet); return; } sig = keyblob + bloblen; if (!create_RSA_sig(privkey.rsa, HASH_SHA1, (unsigned char *)&n_nonce, sizeof(n_nonce), sig, &siglen)) { log0(0, 0, "Error signing nonce"); free(packet); return; } } else { if (!export_EC_key(privkey.ec, keyblob, &bloblen)) { log0(0, 0, "Error exporting public key"); free(packet); return; } sig = keyblob + bloblen; if (!create_ECDSA_sig(privkey.ec, HASH_SHA1, (unsigned char *)&n_nonce, sizeof(n_nonce), sig, &siglen)) { log0(0, 0, "Error signing nonce"); free(packet); return; } } hbreq->bloblen = htons(bloblen); hbreq->siglen = htons(siglen); hbreq->hlen = (sizeof(struct hb_req_h) + bloblen + siglen) / 4; meslen = sizeof(struct uftp_h) + (hbreq->hlen * 4); if (nb_sendto(s, packet, meslen, 0, (struct sockaddr *)hbhost, family_len(*hbhost)) == SOCKET_ERROR) { sockerror(0, 0, "Error sending HB_REQ"); } else { if ((rval = getnameinfo((struct sockaddr *)hbhost, sizeof(union sockaddr_u), addrname, sizeof(addrname), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Sent authenticated HB_REQ to %s:%s", addrname, portstr); } free(packet); } /** * Sends an HB_REQ message to each host listed in the hb_host list */ void send_hb_request(SOCKET s, union sockaddr_u hb_hosts[], int num_hosts, struct timeval *next_hb_time, int hb_interval, uint32_t uid) { unsigned char *packet; struct uftp_h *header; struct hb_req_h *hbreq; char addrname[INET6_ADDRSTRLEN], portstr[PORTNAME_LEN]; int meslen, rval, i; packet = safe_calloc(sizeof(struct uftp_h) + sizeof(struct hb_req_h), 1); header = (struct uftp_h *)packet; hbreq = (struct hb_req_h *)(packet + sizeof(struct uftp_h)); header->version = UFTP_VER_NUM; header->func = HB_REQ; header->src_id = uid; hbreq->func = HB_REQ; hbreq->hlen = sizeof(struct hb_req_h) / 4; for (i = 0; i < num_hosts; i++) { hbreq->nonce = 0; hbreq->bloblen = 0; hbreq->siglen = 0; meslen = sizeof(struct uftp_h) + (hbreq->hlen * 4); if (nb_sendto(s, packet, meslen, 0, (struct sockaddr *)&hb_hosts[i], family_len(hb_hosts[i])) == SOCKET_ERROR) { sockerror(0, 0, "Error sending HB_REQ"); } else { if ((rval = getnameinfo((struct sockaddr *)&hb_hosts[i], sizeof(union sockaddr_u), addrname, sizeof(addrname), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Sent HB_REQ to %s:%s", addrname, portstr); } } free(packet); gettimeofday(next_hb_time, NULL); next_hb_time->tv_sec += hb_interval; } uftp-4.1.5/uftp.h0000644000076400007640000004045712304524207012643 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _UFTP_H #define _UFTP_H #ifdef WINDOWS // same as passing /D _CRT_SECURE_NO_WARNINGS to cl #pragma warning(disable: 4996) #include #include typedef unsigned __int8 uint8_t; typedef __int8 int8_t; typedef unsigned __int16 uint16_t; typedef __int16 int16_t; typedef unsigned __int32 uint32_t; typedef __int32 int32_t; typedef unsigned __int64 uint64_t; typedef __int64 int64_t; #define open(name, ...) _open(name, __VA_ARGS__) #define read(fd, buf, count) _read(fd, buf, count) #define close(fd) _close(fd) #define write(fd, buf, count) _write(fd, buf, count) #define dup2(fd1, fd2) _dup2(fd1, fd2) #define unlink(file) _unlink(file) #define rmdir(dir) _rmdir(dir) #define getpid() _getpid() #define mkdir(dir, mode) _mkdir(dir) #define usleep(t) Sleep((t)/1000) #define sleep(t) Sleep((t)*1000) #define strdup(p) _strdup(p) #define utime(f, t) _utime(f, t) #define isfullpath(str) (((str[1] == ':') && (str[2] == '\\')) || \ ((str[0] == '\\') && (str[1] == '\\'))) #define PATH_SEP '\\' typedef int64_t f_offset_t; typedef struct _stat32i64 stat_struct; typedef struct _utimbuf utim_buf; #define stat_func(name, buf) _stat32i64(name, buf) #define lstat_func(name, buf) _stat32i64(name, buf) #define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) #define lseek_func(fd, offset, whence) _lseeki64(fd, offset, whence) #define snprintf(buf, cnt, ...) _snprintf(buf, cnt, __VA_ARGS__) typedef int socklen_t; #define OPENREAD (O_RDONLY | O_BINARY) #define OPENWRITE (O_WRONLY | O_BINARY) typedef HANDLE mux_t; #define mux_create(mux) (((mux=CreateMutex(NULL,FALSE,NULL))!=NULL)?0:-1) #define mux_destroy(mux) ((CloseHandle(mux)!=0)?0:-1) #define mux_lock(mux) ((WaitForSingleObject(mux,INFINITE)!=WAIT_FAILED)?0:-1) #define mux_unlock(mux) ((ReleaseMutex(mux)!=0)?0:-1) typedef unsigned long thread_t; #define start_thread(id,func,arg) (((id=_beginthread(func,0,arg))!=-1)?0:-1) #define end_thread() _endthread() #define thread_id() GetCurrentThreadId() #define join_thread(id) WaitForSingleObject((HANDLE)(id), INFINITE) #define destroy_thread(id) CloseHandle((HANDLE)(id)) #define THREAD_FUNC void #define THREAD_RETURN return #else // if WINDOWS #include #include #define closesocket(s) close(s) #ifdef VMS pid_t GENERIC_SETSID(void); #define setsid GENERIC_SETSID #define fork vfork typedef unsigned int socklen_t; #define isfullpath(str) (1) #define PATH_SEP ':' #define open(name, flag, mode) open(name, flag, mode, "ctx=stm") #else #define isfullpath(str) (str[0] == '/') #define PATH_SEP '/' #endif typedef int64_t f_offset_t; typedef struct stat stat_struct; typedef struct utimbuf utim_buf; #define stat_func(name, buf) stat(name, buf) #define lstat_func(name, buf) lstat(name, buf) #define lseek_func(fd, offset, whence) lseek(fd, offset, whence) typedef int SOCKET; #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #ifndef INADDR_NONE #define INADDR_NONE -1 #endif #define OPENREAD (O_RDONLY) #define OPENWRITE (O_WRONLY) typedef pthread_mutex_t mux_t; #define mux_create(mux) pthread_mutex_init(&(mux),NULL) #define mux_destroy(mux) pthread_mutex_destroy(&(mux)) #define mux_lock(mux) pthread_mutex_lock(&(mux)) #define mux_unlock(mux) pthread_mutex_unlock(&(mux)) typedef pthread_t thread_t; #define start_thread(id,func,arg) pthread_create(&(id),NULL,func,arg) #define end_thread() pthread_exit(NULL) #define thread_id() pthread_self() #define join_thread(id) pthread_join(id, NULL) #define destroy_thread(id) #define THREAD_FUNC void * #define THREAD_RETURN return NULL #endif // if WINDOWS #define VERSIONSTR "UFTP version 4.1.5 Copyright (C) 2001-2014 Dennis A. Bush" #define UFTP_VER_NUM 0x40 #define ANNOUNCE 1 #define REGISTER 2 #define CLIENT_KEY 3 #define REG_CONF 4 #define KEYINFO 5 #define KEYINFO_ACK 6 #define FILEINFO 7 #define FILEINFO_ACK 8 #define FILESEG 9 #define DONE 10 #define STATUS 11 #define COMPLETE 12 #define DONE_CONF 13 #define HB_REQ 14 #define HB_RESP 15 #define KEY_REQ 16 #define PROXY_KEY 17 #define ENCRYPTED 18 #define ABORT 19 #define CONG_CTRL 20 #define CC_ACK 21 #define FTYPE_REG 0 #define FTYPE_DIR 1 #define FTYPE_LINK 2 #define FTYPE_DELETE 3 #define FTYPE_FREESPACE 4 #define KEY_NONE 0 #define KEY_DES 1 #define KEY_DES_EDE3 2 #define KEY_AES128_CBC 3 #define KEY_AES256_CBC 4 #define KEY_AES128_GCM 5 #define KEY_AES256_GCM 6 #define KEY_AES128_CCM 7 #define KEY_AES256_CCM 8 #define HASH_NONE 0 #define HASH_MD5 1 #define HASH_SHA1 2 #define HASH_SHA256 3 #define HASH_SHA384 4 #define HASH_SHA512 5 #define SIG_NONE 0 #define SIG_HMAC 1 #define SIG_KEYEX 2 #define SIG_AUTHENC 3 #define KEYEX_NONE 0 #define KEYEX_RSA 1 #define KEYEX_ECDH_RSA 2 #define KEYEX_ECDH_ECDSA 3 #define KEYBLOB_RSA 1 #define KEYBLOB_EC 2 #define CURVE_sect163k1 1 #define CURVE_sect163r1 2 #define CURVE_sect163r2 3 #define CURVE_sect193r1 4 #define CURVE_sect193r2 5 #define CURVE_sect233k1 6 #define CURVE_sect233r1 7 #define CURVE_sect239k1 8 #define CURVE_sect283k1 9 #define CURVE_sect283r1 10 #define CURVE_sect409k1 11 #define CURVE_sect409r1 12 #define CURVE_sect571k1 13 #define CURVE_sect571r1 14 #define CURVE_secp160k1 15 #define CURVE_secp160r1 16 #define CURVE_secp160r2 17 #define CURVE_secp192k1 18 #define CURVE_secp192r1 19 #define CURVE_secp224k1 20 #define CURVE_secp224r1 21 #define CURVE_secp256k1 22 #define CURVE_secp256r1 23 #define CURVE_secp384r1 24 #define CURVE_secp521r1 25 #define CURVE_prime192v1 CURVE_secp192r1 #define CURVE_prime256v1 CURVE_secp256r1 #define CC_NONE 0 #define CC_UFTP3 1 #define CC_TFMCC 2 #define CC_PGMCC 3 #define EXT_ENC_INFO 1 #define EXT_TFMCC_DATA_INFO 2 #define EXT_TFMCC_ACK_INFO 3 #define EXT_PGMCC_DATA_INFO 4 #define EXT_PGMCC_NAK_INFO 5 #define EXT_PGMCC_ACK_INFO 6 #define EXT_FREESPACE_INFO 7 #define FLAG_SYNC_MODE 0x01 #define FLAG_SYNC_PREVIEW 0x02 #define FLAG_IPV6 0x04 #define FLAG_CLIENT_AUTH 0x01 #define FLAG_PARTIAL 0x01 #define FLAG_CURRENT_FILE 0x01 #define FLAG_CC_CLR 0x01 #define FLAG_CC_RTT 0x02 #define FLAG_CC_START 0x04 #define FLAG_CC_LEAVE 0x08 #define COMP_STAT_NORMAL 0 #define COMP_STAT_SKIPPED 1 #define COMP_STAT_OVERWRITE 2 #define COMP_STAT_REJECTED 3 #define HB_AUTH_FAILED 0 #define HB_AUTH_OK 1 #define HB_AUTH_CHALLENGE 2 #define MAXFILENAME 100 #define MAXDIRNAME 200 #define MAXPATHNAME 300 #define MAXBACKUPPATHNAME 600 #define MAXPROXYDEST 1000 #define MAXDIR 10 #define MAXSECTION 65536 #define DESTNAME_LEN 80 #define IFNAME_LEN 25 #define PORTNAME_LEN 20 #define MAX_INTERFACES 100 #define MAXMTU 9000 #define PUBKEY_LEN 264 // big enough for a keyblob with RSA-2048 #define RAND_LEN 32 // RFC 5246 #define HMAC_LEN 64 // big enough for SHA-512 #define VERIFY_LEN 12 // RFC 5246 #define MASTER_LEN 48 // RFC 5246 #define MAXIV 16 // big enough for AES256 #define MAXKEY 32 // big enough for AES256 #define KEYBLSIZE 16 // Maximum symetric key blocksize #define DEF_RSA_LEN 512 // Default length of generated RSA keys #define DEF_CURVE CURVE_prime256v1 // Default EC curve #define RSA_EXP 65537 // Public key exponent of generated RSA keys #define SALT_LEN 4 // Length of salt for IV #define GCM_IV_LEN 12 // Length of IV for ciphers in GCM mode #define CCM_IV_LEN 12 // Length of IV for ciphers in CCM mode #define GCM_TAG_LEN 16 // Length of tag for ciphers in GCM mode #define CCM_TAG_LEN 16 // Length of tag for ciphers in CCM mode struct uftp_h { uint8_t version; uint8_t func; uint16_t seq; uint32_t src_id; // ID of sender uint32_t group_id; uint8_t group_inst; // Group restart number uint8_t grtt; // RFC 5401, Unused in upstream messages uint8_t gsize; // Unused in upstream messages uint8_t reserved; }; // sizeof = 16 struct encrypted_h { uint32_t iv_ctr_hi; uint32_t iv_ctr_lo; uint16_t sig_len; uint16_t payload_len; }; // sizeof = 12 + sig_len + payload_len struct announce_h { uint8_t func; // always ANNOUNCE uint8_t hlen; uint8_t flags; uint8_t robust; uint8_t cc_type; uint8_t reserved; uint16_t blocksize; uint32_t tstamp_sec; uint32_t tstamp_usec; //uint32_t publicmcast; // for IPv4 //uint8_t publicmcast[16]; // for IPv6 //uint32_t privatemcast; // for IPv4 //uint8_t privatemcast[16]; // for IPv6 }; // sizeof = 16 + 2*iplen + (encrypted?sizeof(enc_info_he):0) + {uint32_t[]} struct enc_info_he { uint8_t exttype; // always EXT_ENC_INFO uint8_t extlen; uint8_t flags; uint8_t keyextype_sigtype; // & 0xF0 = keyextype, & 0x0F = sigtype uint8_t keytype; uint8_t hashtype; uint16_t keylen; uint16_t dhlen; uint16_t siglen; uint8_t rand1[RAND_LEN]; //uint8_t keyblob[]; //uint8_t dhkey[]; //uint8_t sig[]; }; //sizeof = 44 + keylen + dhlen + siglen struct rsa_blob_t { uint8_t blobtype; // always KEYBLOB_RSA uint8_t reserved; uint16_t modlen; uint32_t exponent; //uint8_t modulus[]; }; //sizeof = 8 + modlen struct ec_blob_t { uint8_t blobtype; // always KEYBLOB_EC uint8_t curve; uint16_t keylen; //uint8_t key[]; }; //sizeof = 4 + keylen struct client_key_h { uint8_t func; // always CLIENT_KEY uint8_t hlen; uint16_t reserved; uint16_t bloblen; uint16_t siglen; //uint8_t keyblob[]; //uint8_t verify[]; }; // sizeof = 8 + bloblen + siglen struct register_h { uint8_t func; // always REGISTER uint8_t hlen; uint16_t keyinfo_len; uint32_t tstamp_sec; uint32_t tstamp_usec; uint8_t rand2[RAND_LEN]; //uint8_t keyinfo[]; // Either an EC key or an RSA encrypted premaster }; // sizeof = 44 + (server_key_len or ec_len) + {uint32_t[]} struct regconf_h { uint8_t func; // always REG_CONF uint8_t hlen; uint16_t reserved; }; // sizeof = 4 + {uint32_t[]} struct keyinfo_h { uint8_t func; // always KEYINFO uint8_t hlen; uint16_t reserved; uint32_t iv_ctr_hi; uint32_t iv_ctr_lo; }; // sizeof = 12 + {destkey[]} struct destkey { uint32_t dest_id; uint8_t groupmaster[MASTER_LEN]; // based on 16 byte blocksize }; // sizeof = 52 */ struct keyinfoack_h { uint8_t func; // always KEYINFO_ACK uint8_t hlen; uint16_t reserved; uint8_t verify_data[VERIFY_LEN]; }; // sizeof = 16 struct fileinfo_h { uint8_t func; // always FILEINFO uint8_t hlen; uint16_t file_id; uint8_t ftype; uint8_t reserved1; uint16_t reserved2; uint8_t namelen; uint8_t linklen; uint16_t hifsize; uint32_t lofsize; uint32_t ftstamp; uint32_t tstamp_sec; uint32_t tstamp_usec; //char name[MAXPATHNAME]; }; // sizeof = 28 + namelen + {uint32_t[]} struct fileinfoack_h { uint8_t func; // always FILEINFO_ACK uint8_t hlen; uint16_t file_id; uint8_t flags; uint8_t reserved1; uint16_t reserved2; uint32_t tstamp_sec; uint32_t tstamp_usec; }; // sizeof = 16 + {uint32_t[]} struct fileseg_h { uint8_t func; // always FILESEG uint8_t hlen; uint16_t file_id; uint16_t section; uint16_t sec_block; }; // sizeof = 8 // Append to FILESEG struct tfmcc_data_info_he { uint8_t exttype; // always EXT_TFMCC_DATA_INFO uint8_t extlen; uint16_t send_rate; uint16_t cc_seq; uint16_t cc_rate; }; // sizeof = 8 // Append to FILESEG struct pgmcc_data_info_he { uint8_t exttype; // always EXT_PGMCC_DATA_INFO uint8_t extlen; uint16_t reserved; uint32_t acker; }; // sizeof = 8 struct done_h { uint8_t func; // always DONE uint8_t hlen; uint16_t file_id; uint16_t section; uint16_t reserved; }; // sizeof = 8 + {uint32_t[]} struct status_h { uint8_t func; // always STATUS uint8_t hlen; uint16_t file_id; uint16_t section; uint16_t reserved; }; // sizeof = 8 // Append to STATUS or CC_ACK struct tfmcc_ack_info_he { uint8_t exttype; // always EXT_TFMCC_ACK_INFO uint8_t extlen; uint8_t flags; // RTT, SS, LEAVE uint8_t reserved; uint16_t cc_seq; uint16_t cc_rate; uint32_t client_id; uint32_t tstamp_sec; uint32_t tstamp_usec; }; // sizeof = 20 // Append to STATUS struct pgmcc_nak_info_he { uint8_t exttype; // always EXT_PGMCC_NAK_INFO uint8_t extlen; uint16_t loss; uint32_t tstamp_sec; uint32_t tstamp_usec; }; // sizeof = 12 // Append to CC_ACK struct pgmcc_ack_info_he { uint8_t exttype; // always EXT_PGMCC_ACK_INFO uint8_t extlen; uint16_t reserved; uint16_t loss; uint16_t max_seq; uint32_t ack_bitmap; uint32_t tstamp_sec; uint32_t tstamp_usec; }; // sizeof = 20 struct complete_h { uint8_t func; // always COMPLETE uint8_t hlen; uint16_t file_id; uint8_t status; uint8_t reserved1; uint16_t reserved2; }; // sizeof = 8 + {uint32_t[]} struct freespace_info_he { uint8_t exttype; // always EXT_FREESPACE_INFO uint8_t extlen; uint16_t reserved; uint32_t freespace_hi; uint32_t freespace_lo; }; // sizeof = 12 struct doneconf_h { uint8_t func; // always DONE_CONF uint8_t hlen; uint16_t reserved; }; // sizeof = 4 + {uint32_t[]} struct abort_h { uint8_t func; // always ABORT uint8_t hlen; uint8_t flags; uint8_t reserved; uint32_t host; char message[300]; // TODO: define error codes }; // sizeof = 308 struct hb_req_h { uint8_t func; // always HB_REQ uint8_t hlen; uint16_t reserved; uint16_t bloblen; uint16_t siglen; uint32_t nonce; //uint8_t keyblob[]; //uint8_t verify[]; }; // sizeof = 12 + bloblen + siglen struct hb_resp_h { uint8_t func; // always HB_RESP uint8_t hlen; uint8_t authenticated; uint8_t reserved; uint32_t nonce; }; // sizeof = 8 struct key_req_h { uint8_t func; // always KEY_REQ uint8_t hlen; uint16_t reserved; }; // sizeof = 4 struct proxy_key_h { uint8_t func; // always PROXY_KEY uint8_t hlen; uint16_t bloblen; uint16_t dhlen; uint16_t siglen; uint32_t nonce; //uint8_t keyblob[]; //uint8_t dhkey[]; //uint8_t verify[]; }; // sizeof = 12 + bloblen + dhlen + siglen struct cong_ctrl_h { uint8_t func; // always CONG_CTRL uint8_t hlen; uint16_t reserved; uint16_t cc_seq; uint16_t cc_rate; uint32_t tstamp_sec; uint32_t tstamp_usec; }; // sizeof = 16 + {cc_item[]} struct cc_item { uint32_t dest_id; uint8_t flags; // CLR, RTT, START, LEAVE uint8_t rtt; // RFC 5401 uint16_t reserved; }; // sizeof = 8 struct cc_ack_h { uint8_t func; // always CC_ACK uint8_t hlen; uint16_t reserved; }; // sizeof = 4 + tfmcc_info_he or pgmcc_info_ack_he #endif // _UFTP_H uftp-4.1.5/proxy_upstream.h0000644000076400007640000000425112250244021014747 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_UPSTREAM_H #define _PROXY_UPSTREAM_H void handle_announce(struct pr_group_list_t *group, const union sockaddr_u *src, unsigned char *packet, unsigned packetlen); void handle_regconf(struct pr_group_list_t *group, const unsigned char *message, unsigned meslen); void handle_keyinfo(struct pr_group_list_t *group, unsigned char *message, unsigned meslen, uint32_t src_id); void send_register(struct pr_group_list_t *group, int pendidx); void send_clientkey(struct pr_group_list_t *group); void send_keyinfo_ack(struct pr_group_list_t *group); void send_fileinfo_ack(struct pr_group_list_t *group, int pendidx); void send_status(struct pr_group_list_t *group, int pendidx); void send_complete(struct pr_group_list_t *group, int pendidx); #endif // _PROXY_UPSTREAM_H uftp-4.1.5/server_transfer.c0000644000076400007640000007415112304524207015070 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2014 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include "win_func.h" #else // if WINDOWS #include #include #include #include #endif #include "server.h" #include "server_common.h" #include "server_transfer.h" /** * Send out DONE_CONF messages specifiying all completed clients. * Returns 1 on success, 0 on fail */ int send_doneconf(const struct finfo_t *finfo, int attempt) { unsigned char *buf; struct uftp_h *header; struct doneconf_h *doneconf; uint32_t *idlist; int rval; if (finfo->file_id != 0) { return 1; } buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; doneconf = (struct doneconf_h *)(buf + sizeof(struct uftp_h)); set_uftp_header(header, DONE_CONF, finfo->group_id, finfo->group_inst, grtt, destcount); doneconf->func = DONE_CONF; doneconf->hlen = sizeof(struct doneconf_h) / 4; idlist = (uint32_t *)((uint8_t *)doneconf + (doneconf->hlen * 4)); rval = send_multiple(finfo, buf, DONE_CONF, attempt, idlist, DEST_DONE, (keytype != KEY_NONE), &receive_dest, 0); free(buf); return rval; } /** * Send out DONE messages specifiying active clients that haven't yet responded. * The grtt is being passed in because multiple threads could be touching it. * Returns 1 on success, 0 on fail */ int send_done(const struct finfo_t *finfo, int attempt, int section, double l_grtt) { unsigned char *buf; struct uftp_h *header; struct done_h *done; uint32_t *idlist; int rval; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; done = (struct done_h *)(buf + sizeof(struct uftp_h)); set_uftp_header(header, DONE, finfo->group_id, finfo->group_inst, l_grtt, destcount); done->func = DONE; done->hlen = sizeof(struct done_h) / 4; done->file_id = htons(finfo->file_id); done->section = htons(section); idlist = (uint32_t *)((uint8_t *)done + (done->hlen * 4)); rval = send_multiple(finfo, buf, DONE, attempt, idlist, DEST_ACTIVE, (keytype != KEY_NONE), &receive_dest, 0); free(buf); return rval; } /** * Creates the body of a CONG_CTRL message * This is done separate from sending the message so that the receiving thread * can perform this part. We do this because the receiving thread checks * timeouts, and because the referenced data structures are now only * read/written in one thread, so we don't have to lock when we do this part. */ void create_cc_list(unsigned char **body, int *len) { struct cc_item *list; int *has_rtt, *no_rtt, has_rtt_len, no_rtt_len, maxlist, count, i; *body = safe_calloc(blocksize, 1); has_rtt = safe_calloc(MAXDEST, sizeof(int)); no_rtt = safe_calloc(MAXDEST, sizeof(int)); for (has_rtt_len = 0, no_rtt_len = 0, i = 0; i < destcount; i++) { if (i == clr) continue; if (destlist[i].rtt_sent) { has_rtt[has_rtt_len++] = i; } else { no_rtt[no_rtt_len++] = i; } } maxlist = blocksize / sizeof(struct cc_item); list = (struct cc_item *)*body; count = 0; if (clr != -1) { list[count].dest_id = destlist[clr].id; list[count].flags = FLAG_CC_CLR | FLAG_CC_RTT | (slowstart ? FLAG_CC_START : 0); list[count].rtt = quantize_grtt(destlist[clr].rtt); destlist[clr].rtt_sent = 1; count++; } for (i = 0; (i < no_rtt_len) && (count < maxlist); i++) { list[count].dest_id = destlist[no_rtt[i]].id; list[count].flags = FLAG_CC_RTT | (slowstart ? FLAG_CC_START : 0); list[count].rtt = quantize_grtt(destlist[no_rtt[i]].rtt); count++; destlist[i].rtt_sent = 1; } for (i = 0; (i < has_rtt_len) && (count < maxlist); i++) { list[count].dest_id = destlist[has_rtt[i]].id; list[count].flags = FLAG_CC_RTT | (slowstart ? FLAG_CC_START : 0); list[count].rtt = quantize_grtt(destlist[has_rtt[i]].rtt); count++; destlist[i].rtt_sent = 1; } *len = count * sizeof(struct cc_item); free(has_rtt); free(no_rtt); } /** * Send out a CONG_CTRL message */ void send_cong_ctrl(const struct finfo_t *finfo, double l_grtt, uint16_t l_cc_seq, uint32_t l_cc_rate, unsigned char *body, int len) { unsigned char *buf, *bodyptr, *encrypted, *outpacket; struct uftp_h *header; struct cong_ctrl_h *cong_ctrl; struct timeval now; int payloadlen, enclen; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; cong_ctrl = (struct cong_ctrl_h *)(buf + sizeof(struct uftp_h)); set_uftp_header(header, CONG_CTRL, finfo->group_id, finfo->group_inst, l_grtt, destcount); header->seq = htons(send_seq++); cong_ctrl->func = CONG_CTRL; cong_ctrl->hlen = sizeof(struct cong_ctrl_h) / 4; cong_ctrl->cc_seq = htons(l_cc_seq); cong_ctrl->cc_rate = htons(quantize_rate(l_cc_rate)); gettimeofday(&now, NULL); cong_ctrl->tstamp_sec = htonl(now.tv_sec); cong_ctrl->tstamp_usec = htonl(now.tv_usec); bodyptr = (unsigned char *)cong_ctrl + (cong_ctrl->hlen * 4); memcpy(bodyptr, body, len); payloadlen = (cong_ctrl->hlen * 4) + len; if (keytype != KEY_NONE) { encrypted = NULL; if (!encrypt_and_sign(buf, &encrypted, payloadlen, &enclen, keytype, groupkey, groupsalt, &ivctr, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, privkey, privkeylen)) { log0(finfo->group_id, finfo->file_id, "Error encrypting CONG_CTRL"); free(buf); return; } outpacket = encrypted; payloadlen = enclen; } else { encrypted = NULL; outpacket = buf; } if (nb_sendto(sock, outpacket, payloadlen + sizeof(struct uftp_h), 0, (struct sockaddr *)&receive_dest, family_len(receive_dest)) == SOCKET_ERROR) { sockerror(finfo->group_id, finfo->file_id, "Error sending CONG_CTRL"); } log4(finfo->group_id, finfo->file_id, "Sent CONG_CTRL, seq %d", l_cc_seq); free(buf); free(encrypted); } /** * Handle a EXT_FREESPACE_INFO extension in a COMPLETE */ void handle_freespace_info(const struct freespace_info_he *freespace, int hostidx) { destlist[hostidx].freespace = ntohl(freespace->freespace_lo); destlist[hostidx].freespace |= (int64_t)ntohl(freespace->freespace_hi)<<32; } /** * Process an expected COMPLETE message */ void handle_complete(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx) { const struct complete_h *complete; const struct freespace_info_he *freespace; const uint8_t *he; const uint32_t *idlist; int clientcnt, clientidx, dupmsg, isproxy, i; unsigned extlen; char status[20]; complete = (const struct complete_h *)message; idlist = (const uint32_t *)(message + (complete->hlen * 4)); clientcnt = (meslen - (complete->hlen * 4)) / 4; if ((meslen < (complete->hlen * 4U)) || ((complete->hlen * 4U) < sizeof(struct complete_h))) { log1(finfo->group_id, finfo->file_id, "Rejecting COMPLETE from %s: " "invalid message size", destlist[hostidx].name); return; } if (ntohs(complete->file_id) != finfo->file_id) { if (finfo->file_id == 0) { return; // Reject silently } log1(finfo->group_id, finfo->file_id, "Rejecting COMPLETE from %s: " "invalid file ID %04X, expected %04X ", destlist[hostidx].name, ntohs(complete->file_id), finfo->file_id); if (clientcnt > 0) { for (i = 0; i < clientcnt; i++) { clientidx = find_client(idlist[i]); if (clientidx == -1) { log2(finfo->group_id, finfo->file_id, " For client %08X", ntohl(idlist[i])); } else { log2(finfo->group_id, finfo->file_id, " For client %s", destlist[clientidx].name); } } } return; } freespace = NULL; if (complete->hlen * 4U > sizeof(struct complete_h)) { he = (const uint8_t *)complete + sizeof(struct complete_h); if (*he == EXT_FREESPACE_INFO) { freespace = (const struct freespace_info_he *)he; extlen = freespace->extlen * 4U; if ((extlen > (complete->hlen * 4U) - sizeof(struct complete_h)) || extlen < sizeof(struct freespace_info_he)) { log1(finfo->group_id, finfo->file_id, "Rejecting COMPLETE from %s: invalid extension size", destlist[hostidx].name); return; } } } dupmsg = (destlist[hostidx].status == DEST_DONE); isproxy = (destlist[hostidx].clientcnt != -1); destlist[hostidx].comp_status = complete->status; switch (complete->status) { case COMP_STAT_NORMAL: strncpy(status, "", sizeof(status)); break; case COMP_STAT_SKIPPED: strncpy(status, "(skipped)", sizeof(status)); break; case COMP_STAT_OVERWRITE: strncpy(status, "(overwritten)", sizeof(status)); break; case COMP_STAT_REJECTED: strncpy(status, "(rejected)", sizeof(status)); break; } log2(finfo->group_id, finfo->file_id, "Got COMPLETE%s%s from %s %s", status, (dupmsg && !isproxy) ? "+" : "", (isproxy) ? "proxy" : "client", destlist[hostidx].name); if (destlist[hostidx].clientcnt != -1) { for (i = 0; i < clientcnt; i++) { clientidx = find_client(idlist[i]); if (clientidx == -1) { log1(finfo->group_id, finfo->file_id, "Client %08X via proxy %s not found", ntohl(idlist[i]), destlist[hostidx].name); } else if (destlist[clientidx].proxyidx != hostidx) { log1(finfo->group_id, finfo->file_id, "Client %s found via proxy %s, expected proxy %s", destlist[clientidx].name, destlist[destlist[clientidx].proxyidx].name, destlist[hostidx].name); } else { dupmsg = (destlist[clientidx].status == DEST_DONE); log2(finfo->group_id, finfo->file_id, " For client%s %s", dupmsg ? "+" : "", destlist[clientidx].name); finfo->deststate[clientidx].conf_sent = 0; destlist[clientidx].status = DEST_DONE; destlist[clientidx].comp_status = complete->status; gettimeofday(&finfo->deststate[clientidx].time, NULL); } } } else { finfo->deststate[hostidx].conf_sent = 0; destlist[hostidx].status = DEST_DONE; gettimeofday(&finfo->deststate[hostidx].time, NULL); } if (freespace) { handle_freespace_info(freespace, hostidx); } } /** * Handle a EXT_TFMCC_ACK_INFO extension in a STATUS or CC_ACK * The mux_main mutex should already be locked */ void handle_tfmcc_ack_info(const struct finfo_t *finfo, const struct tfmcc_ack_info_he *tfmcc, int hostidx) { struct timeval now, msgtime; int flag_ss, flag_rtt, rate_1grtt; unsigned client_rate; double l_adv_grtt; gettimeofday(&now, NULL); msgtime.tv_sec = ntohl(tfmcc->tstamp_sec); msgtime.tv_usec = ntohl(tfmcc->tstamp_usec); destlist[hostidx].rtt = diff_usec(now, msgtime) / 1000000.0; if (destlist[hostidx].rtt < CLIENT_RTT_MIN) { destlist[hostidx].rtt = CLIENT_RTT_MIN; } destlist[hostidx].rtt_measured = 1; destlist[hostidx].rtt_sent = 0; log4(finfo->group_id,finfo->file_id, " rtt = %.6f", destlist[hostidx].rtt); client_rate = unquantize_rate(ntohs(tfmcc->cc_rate)); flag_ss = ((tfmcc->flags & FLAG_CC_START) != 0); flag_rtt = ((tfmcc->flags & FLAG_CC_RTT) != 0); // TODO: should we be checking the advertised GRTT instead of the real GRTT? if (destlist[hostidx].rtt > grtt) { grtt = destlist[hostidx].rtt; } if (!flag_ss) { slowstart = 0; } if (hostidx == clr) { log3(finfo->group_id, finfo->file_id, "Got clr CC response for round %d", ntohs(tfmcc->cc_seq)); rate_1grtt = (int)(datapacketsize / grtt); if (!flag_ss && !flag_rtt) { client_rate = (unsigned)((double)client_rate * (adv_grtt / destlist[hostidx].rtt)); } if ((client_rate > (unsigned)rate + rate_1grtt) && !slowstart) { rate += rate_1grtt; } else { rate = client_rate; } packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); last_clr_time = now; } else { log3(finfo->group_id, finfo->file_id, "Got CC response for round %d", ntohs(tfmcc->cc_seq)); if (client_rate < cc_rate) { cc_rate = (int)(client_rate * 0.9); } if (!flag_ss && !flag_rtt) { client_rate = (unsigned)((double)client_rate * (adv_grtt / destlist[hostidx].rtt)); } if ((client_rate < (unsigned)rate) || (clr == -1)) { log3(finfo->group_id, finfo->file_id, "Selected new clr %s", destlist[hostidx].name); if (!clr_drop) { rate = client_rate; packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); rate_change = 1; } else if (client_rate < (unsigned)rate) { new_rate = client_rate; } else { new_rate = rate; } clr = hostidx; last_clr_time = now; } } l_adv_grtt = (double)datapacketsize / rate; if (l_adv_grtt < grtt) { l_adv_grtt = grtt; } if (l_adv_grtt > adv_grtt) { adv_grtt = l_adv_grtt; } log4(finfo->group_id, finfo->file_id, "rate = %d", rate); } /** * Process an expected STATUS message * Sets *status_postion to the lowest numbered packet NAKed in this message */ void handle_status(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx, int *got_naks) { const struct status_h *status; const struct tfmcc_ack_info_he *tfmcc; const uint8_t *naklist, *he; unsigned section, current_section, naks, section_offset, blocks_this_sec; unsigned nakidx, listidx, nak_bytes, i, j; unsigned extlen; status = (const struct status_h *)message; naklist = ((const uint8_t *)status) + (status->hlen * 4); section = ntohs(status->section); if ((meslen < (status->hlen * 4U)) || ((status->hlen * 4U) < sizeof(struct status_h))) { log1(finfo->group_id, finfo->file_id, "Rejecting STATUS from %s: " "invalid message size", destlist[hostidx].name); return; } if (ntohs(status->file_id) != finfo->file_id) { log1(finfo->group_id, finfo->file_id, "Rejecting STATUS from %s: " "invalid file ID %04X, expected %04X ", destlist[hostidx].name, ntohs(status->file_id), finfo->file_id ); return; } tfmcc = NULL; if (status->hlen * 4U > sizeof(struct status_h)) { he = (const uint8_t *)status + sizeof(struct status_h); if (*he == EXT_TFMCC_ACK_INFO) { tfmcc = (const struct tfmcc_ack_info_he *)he; extlen = tfmcc->extlen * 4U; if ((extlen > (status->hlen * 4U) - sizeof(struct status_h)) || extlen < sizeof(struct tfmcc_ack_info_he)) { log1(finfo->group_id, finfo->file_id, "Rejecting STATUS from %s: invalid extension size", destlist[hostidx].name); return; } } } if (section >= finfo->big_sections) { section_offset = (finfo->big_sections * finfo->secsize_big) + ((section - finfo->big_sections) * finfo->secsize_small); blocks_this_sec = finfo->secsize_small; } else { section_offset = section * finfo->secsize_big; blocks_this_sec = finfo->secsize_big; } if (meslen < (status->hlen * 4U) + (blocks_this_sec / 8) + 1) { log1(finfo->group_id, finfo->file_id, "Rejecting STATUS from %s: " "invalid message size", destlist[hostidx].name); return; } if (mux_lock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to lock mutex in handle_status"); return; } if ((cc_type == CC_TFMCC) && tfmcc) { handle_tfmcc_ack_info(finfo, tfmcc, hostidx); } if (current_position < finfo->blocks) { if (current_position >= finfo->big_sections * finfo->secsize_big) { current_section = ((current_position - (finfo->big_sections * finfo->secsize_big)) / finfo->secsize_small) + finfo->big_sections; } else { current_section = current_position / finfo->secsize_big; } if (section == current_section) { // Don't accept if it's for the current section log3(finfo->group_id, finfo->file_id, "Dropping STATUS for section %d", section); if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex in handle_status"); } return; } } // Count the NAKs first to see if there's an excessive amount nak_bytes = blocks_this_sec / 8; for (naks = 0, i = 0; i < nak_bytes; i++) { for (j = 0; j < 8; j++) { if ((naklist[i] & (1 << j)) != 0) { naks++; } } } if ((naks * 100 / blocks_this_sec) > (unsigned)max_nak_pct) { destlist[hostidx].max_nak_exceed++; } if (destlist[hostidx].max_nak_exceed >= max_nak_cnt) { log1(finfo->group_id, finfo->file_id, "Got excessive NAKs (%d) " "for section %d from %s %s, aborting", naks, section, (destlist[hostidx].clientcnt != -1) ? "proxy" : "client", destlist[hostidx].name); destlist[hostidx].status = DEST_ABORT; send_abort(finfo, "Excessive NAKs received", &receive_dest, destlist[hostidx].id, (keytype != KEY_NONE), 0); if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex in handle_status"); } return; } // Now record the NAKs for (naks = 0, i = 0; i < blocks_this_sec; i++) { // Each bit represents a NAK; check each one // Simplified: (naklist[listidx / 8] & (1 << (listidx % 8))) nakidx = i + section_offset; listidx = i; if ((naklist[listidx >> 3] & (1 << (listidx & 7))) != 0) { log4(finfo->group_id, finfo->file_id, "Got NAK for %d", nakidx); finfo->naklist[nakidx] = 1; *got_naks = 1; naks++; } } if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex in handle_status"); } log2(finfo->group_id, finfo->file_id, "Got %d NAKs for section %d from %s %s", naks, section, (destlist[hostidx].clientcnt != -1) ? "proxy" : "client", destlist[hostidx].name); destlist[hostidx].status = DEST_ACTIVE_NAK; } /** * Process an expected CC_ACK message */ void handle_cc_ack(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx) { const struct cc_ack_h *cc_ack; const struct tfmcc_ack_info_he *tfmcc; const uint8_t *he; unsigned extlen; cc_ack = (const struct cc_ack_h *)message; if ((meslen < (cc_ack->hlen * 4U)) || ((cc_ack->hlen * 4U) < sizeof(struct cc_ack_h))) { log1(finfo->group_id, finfo->file_id, "Rejecting CC_ACK from %s: " "invalid message size", destlist[hostidx].name); return; } tfmcc = NULL; if (cc_ack->hlen * 4U > sizeof(struct cc_ack_h)) { he = (const uint8_t *)cc_ack + sizeof(struct cc_ack_h); if (*he == EXT_TFMCC_ACK_INFO) { tfmcc = (const struct tfmcc_ack_info_he *)he; extlen = tfmcc->extlen * 4U; if ((extlen > (cc_ack->hlen * 4U) - sizeof(struct cc_ack_h)) || extlen < sizeof(struct tfmcc_ack_info_he)) { log1(finfo->group_id, finfo->file_id, "Rejecting CC_ACK from %s: invalid extension size", destlist[hostidx].name); return; } } } log3(finfo->group_id, finfo->file_id, "Got CC_ACK from %s", destlist[hostidx].name); if ((cc_type == CC_TFMCC) && tfmcc) { if (mux_lock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to lock mutex in handle_cc_ack"); return; } handle_tfmcc_ack_info(finfo, tfmcc, hostidx); if (mux_unlock(mux_main)) { log0(finfo->group_id, finfo->file_id, "Failed to unlock mutex in handle_cc_ack"); return; } } } /** * Sends out a data packet. All headers should be populated */ int send_data(const struct finfo_t *finfo, unsigned char *packet, int datalen, unsigned char *encpacket) { struct uftp_h *header; struct fileseg_h *fileseg; int payloadlen, enclen; unsigned char *outpacket; header = (struct uftp_h *)packet; fileseg = (struct fileseg_h *)(packet + sizeof(struct uftp_h)); header->seq = htons(send_seq++); payloadlen = (fileseg->hlen * 4) + datalen; if (keytype != KEY_NONE) { if (!encrypt_and_sign(packet, &encpacket, payloadlen, &enclen, keytype, groupkey, groupsalt, &ivctr, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, privkey, privkeylen)) { log0(finfo->group_id, finfo->file_id, "Error encrypting FILESEG"); return 0; } outpacket = encpacket; payloadlen = enclen; } else { outpacket = packet; } if (nb_sendto(sock, outpacket, payloadlen + sizeof(struct uftp_h), 0, (struct sockaddr *)&receive_dest, family_len(receive_dest)) == SOCKET_ERROR) { sockerror(finfo->group_id, finfo->file_id, "Error sending FILESEG"); return 0; } return 1; } /** * Print the final statistics for the given file to the status file */ void print_status_file(const struct finfo_t *finfo, struct timeval start_time) { double elapsed_time, throughput; int i; if (finfo->file_id == 0) { fprintf(status_file, "HSTATS;target;copy;overwrite;" "skip;totalMB;time;speedKB/s\n"); for (i = 0; i < destcount; i++) { if (destlist[i].clientcnt >= 0) { continue; } if (destlist[i].total_time > 0) { throughput = destlist[i].total_size / destlist[i].total_time / 1024; } else { throughput = 0; } fprintf(status_file, "STATS;%s;%d;%d;%d;%sMB;%.3f;%.2fKB/s\n", destlist[i].name, destlist[i].num_copy, destlist[i].num_overwrite, destlist[i].num_skip, printll(destlist[i].total_size / 1048576), destlist[i].total_time, throughput); } return; } for (i = 0; i < destcount; i++) { if (destlist[i].clientcnt >= 0) { continue; } fprintf(status_file, "RESULT;%s;%s;%sKB;", destlist[i].name, finfo->destfname, printll(finfo->size / 1024)); switch (destlist[i].status) { case DEST_MUTE: fprintf(status_file, "mute;\n"); break; case DEST_LOST: fprintf(status_file, "lost;\n"); break; case DEST_ABORT: fprintf(status_file, "aborted;\n"); break; case DEST_DONE: if (sync_preview) { throughput = rate / 8; elapsed_time = finfo->size / (throughput * 1024); } else { elapsed_time = diff_usec(finfo->deststate[i].time, start_time) / 1000000.0; if (elapsed_time > 0) { throughput = finfo->size / elapsed_time / 1024; } else { throughput = 0; } } switch (destlist[i].comp_status) { case COMP_STAT_NORMAL: fprintf(status_file, "copy;%.2fKB/s\n", throughput); destlist[i].num_copy++; destlist[i].total_time += elapsed_time; destlist[i].total_size += finfo->size; break; case COMP_STAT_SKIPPED: fprintf(status_file, "skipped;\n"); destlist[i].num_skip++; break; case COMP_STAT_OVERWRITE: fprintf(status_file, "overwritten;%.2fKB/s\n", throughput); destlist[i].num_overwrite++; destlist[i].total_time += elapsed_time; destlist[i].total_size += finfo->size; break; case COMP_STAT_REJECTED: fprintf(status_file, "rejected;\n"); break; default: fprintf(status_file, "Unknown;\n"); break; } if (destlist[i].freespace != -1) { fprintf(status_file, "FREESPACE;%s;%s\n", finfo->destfname, printll(destlist[i].freespace)); } break; default: fprintf(status_file, "Unknown;\n"); break; } } } /** * Print the final statistics for the given file */ void print_status(const struct finfo_t *finfo, struct timeval start_time) { struct timeval done_time; double elapsed_time; int i; if (status_file) { print_status_file(finfo, start_time); } if (finfo->file_id == 0) { log0(finfo->group_id, finfo->file_id, "Group complete"); return; } log0(finfo->group_id, finfo->file_id, "Transfer status:"); for (done_time = start_time, i = 0; i < destcount; i++) { if (destlist[i].clientcnt >= 0) { continue; } clog0(finfo->group_id, finfo->file_id, "Host: %-15s Status: ", destlist[i].name); switch (destlist[i].status) { case DEST_MUTE: slog0("Mute"); break; case DEST_LOST: slog0("Lost connection"); break; case DEST_ABORT: slog0("Aborted"); break; case DEST_DONE: switch (destlist[i].comp_status) { case COMP_STAT_NORMAL: if (diff_usec(finfo->deststate[i].time, done_time) > 0) { done_time = finfo->deststate[i].time; } elapsed_time = diff_usec(finfo->deststate[i].time, start_time) / 1000000.0; slog0("Completed time: %7.3f seconds", elapsed_time); break; case COMP_STAT_SKIPPED: slog0("Skipped"); break; case COMP_STAT_OVERWRITE: if (diff_usec(finfo->deststate[i].time, done_time) > 0) { done_time = finfo->deststate[i].time; } elapsed_time = diff_usec(finfo->deststate[i].time, start_time) / 1000000.0; slog0("Completed(overwritten) time: %7.3f seconds", elapsed_time); break; case COMP_STAT_REJECTED: slog0("Rejected"); break; default: slog0("Unknown completion status: %d", destlist[i].comp_status); break; } if (destlist[i].freespace != -1) { log0(finfo->group_id, finfo->file_id, " Free space on host: %s bytes", printll(destlist[i].freespace)); } break; default: slog0("Unknown code: %d", destlist[i].status); break; } } elapsed_time = diff_usec(done_time, start_time) / 1000000.0; log1(finfo->group_id, finfo->file_id, "Total elapsed time: %.3f seconds", elapsed_time); log1(finfo->group_id, finfo->file_id, "Overall throughput: %.2f KB/s", (elapsed_time != 0) ? (finfo->size / elapsed_time / 1024) : 0); } uftp-4.1.5/Changes.txt0000644000076400007640000006676412304524207013636 0ustar dbushdbushVersion 4.1.5 - 3/1/2014 Fixed casting bug in server TFMCC operations when calculating client rate. Fixed server proxy bug where group round trip time was being read from client messages when it should only be read from server messages. Version 4.1.4 - 12/5/2013 During a restart session, a bug caused the full file to be resent on the first pass, and client wouldn't send back NAKs for the first session. This has been fixed. Fixed incorrect reading of client ID lists in DONE, DONE_CONF, and CONG_CTRL messages. Added group ID and file ID to server logging when timestamps are enabled. Added more warning checks for Linux and corrected warnings. Cleaned up error checking code for malloc and calloc calls. Version 4.1.3 - 10/13/2013 Since 4.0, compilation failed on MacOSX. This has been fixed. Version 4.1.2 - 8/20/2013 Fixed client pathname checking that disallows ".." path elements. Server wasn't writing clients to the restart file that didn't send a COMPLETE message for the session. Bug fixed. Version 4.1.1 - 8/8/2013 Fixed byte ordering issue in EXT_FREESPACE_INFO extension. Version 4.1 - 7/30/2013 Changed the way the server handles NAKs from the clients. Instead of rewinding its current position as soon as NAKs are received, the server will wait until the end of the pass, then start a new pass. This more closely resembles version 3.x behavior. Added special metafiles @DELETE:{filename}, which deletes filename on the client, and @FREESPACE, which reports the available free disk space on the client's primary destination directory. Upon receiving a FILEINFO, a client will abort if it doesn't have enough free disk space to receive the file. Added -S option to server to write parsable status information to a separate log file. This information was previously available only with the -z option, and -z will no longer print this info without -S. Added -N option which specifies the maximum NAK percentage a client may report before potentially getting dropped. Also added related -m option, which specifies the number of times a client can exceed the maximum NAK percentage before it is dropped. Reinstated the -W option, which specifies the maximum file transfer time as a percentage of the optimal time. Added -i option to client to allow moving files one at a time from the temp directory to the dest directory. This option is necessary to prevent previously received files in dest from being deleted when full directories are received. Added -u option to server to specify outgoing UDP port number. Added log rolling to the server, client, and proxy. The -g option specifies the max log file size in MB before rolling, and the -n option specifies the number of rolled log files to save. The server may now specify clients by their IPv4 name/address as well as their ID, assuming the clients in question use their IPv4 address as their ID, making for cleaner backward compatibility with version 3.x. Fixed several bugs in the TFMCC implementation: Fixed counter wraparound issue. Fixed crash when more than a few hundred clients are active. Fixed bug which caused rate to be set to 0. Fixed crash when sending an empty file. Added signal handlers in server so that it will save restart info if the user interrupts it. Fixed issue with printing of client IDs in server. Dependency fixes in makefile Version 4.0 - 4/27/2013 Added support for IPv6. On systems that support dual stack IPv4/IPv6 sockets, clients and proxies may listen on multicast groups for both IP versions. Dynamically determine round trip time to clients and base control message retransmissions and timeouts on that. Old server timing options -A, -S, -a, -s, -r, -d, -W, and -m are no longer used, with -r and -s options repurposed to put constraints on round trip time calculations and control message retransmission counts. The server no longer waits for client responses in the middle of sending a file. Multiple threads are used to send data packets while reading client responses, resulting in shorter overall transmission times. Also, clients won't send back STATUS messages unless they require retranmissions of data packets, reducing to amount of back traffic the server must handle. Replaced simple congestion control scheme with TCP Friendly Multicast Congestion Control (TFMCC) as specified in RFC 4654. Upgraded available security protocols, including support for Elliptic Curve Diffe-Hellman key exchange, Elliptic Curve DSA signatures, AES in GCM and CCM authenticated modes, and SHA-384 and SHA-512 hashing. As a result of this, the key fingerprinting algorithm has changed, so existing RSA keys will have different signatures. Clients and proxies are now always identified by a 32-bit ID instead of their local IPv4 address. This ID will be derived from either the local IPv4 or IPv6 address if not explicitly specified. The format of the server list file on the client and the client/proxy list files on the server have changed as a result of the new method of identifying clients. See the man pages for more details. Modified protocol to support the above new features and to be more extendable in the face of future upgrades. While these changes break backward compatibility with 3.x versions, the added support for header extensions should prevent future instances of this. Version 3.7.2 - 3/17/2013 When a client aborts and isn't using a temp directory, delete the current file Version 3.7.1 - 10/21/2012 Fixed bug where clients would occasionally fail to register in open group mode if the client doesn't have a UID assigned. First appeared in version 3.4.3. With a congestion control file in use, the speed wouldn't increase if the max_rate parameter was not specified in the congestion control config file. First appeared in version 3.6. Fixed. Under Windows, a postreceive script with a .bat extension wouldn't execute. Bug fixed. Version 3.7 - 6/28/2012 Added the much-requested option to the client to run an external command upon receiving a file. See the -s option. Increased the maximum number of recognized network interfaces to 100. Fixed data corruption bug caused by a file write error on the client. Version 3.6.1 - 12/12/2011 Reverted the change in 3.6 that allowed the client and proxy to bind only to one interface. Doing so breaks multicast on UNIX-like systems. Version 3.6 - 12/3/2011 Added option (-N) to client and proxy to specify process priority. Default behavior change: prior to this release on UNIX-like systems, the client and proxy had a default nice() value of -20. The new default is 0. Added max_rate parameter to congestion control config file. Previously, with congestion control enabled, the initial rate would be the max rate. This parameter allows a max rate to be set separate from the initial rate. On client and proxy, if only interface is specified to -I (or if -I is not specified and only one non-loopback interface is detected), the client/proxy will bind only to that interface. If more than one is specified or detected, it will bind to all interfaces. Prior to this release, it would always bind to all interfaces. Explicitly disable MTU discovery on systems that support it. Since the server specifies the MTU, it doesn't make sense to allow this. When using a congestion control file, it is reread at each DONE interval. This allows an external process to adjust the parameters on the fly based on network conditions or business rules. Fixed bug where a proxy wouldn't properly handle an ABORT from a client. Fixed bug that would cause all clients to abort on when max file transfer timeout was exceeded. Fixed server bug where -j wouldn't work if specified before -H. Version 3.5.1 - 7/10/2011 Send COMPLETE(rejected) instead of ABORT for file/path issues. Allow server to send broadcast packets. Improved memory management in server. Proxy wasn't propigating COMPLETE status upsteam. Fixed. Filenames passed to server are no longer checked on startup. They are instead checked as they are handled. Fixed proxy bug in open group mode where a client's address isn't detected. Fixed a few proxy bounds checking issues. Version 3.5 - 6/17/2011 Added sync mode - Incoming files won't overwrite existing files unless the existing file is older. In this mode the server's output displays whether a file is copied, overwritten, or skipped, and also displays a count of each at the end of the session. See -z on the server. Added sync preview mode - like sync mode, except no files are actually transferred. The server displays the status of each file (copy, overwrite, or skip) had it actually been copied. See -Z on the server. When using -D on the server to specify the destination file name, an absolute pathname may be specified. Clients must be configured to allow incoming absolute pathnames. Allow clients to specify multiple destination directories. The client will allow an incoming file with an absolute pathname if it matches at least one destination directory. Added a simple congestion control method controled by a config file. See the -C option to the server. Added backup directory option to client. If an incoming file would overwrite an existing file, the existing file is backed up. See -A on the client. Added option to client to use an individual temp file for each incoming file as opposed to a temp directory for all files. See -t on the client. Added configurable logging levels to server, client, and proxy. See -x. Added ability to send proxy heartbeat messages from clients. See -H and -h. Fixed bug where clients assigned a UID don't register properly. Fixed crash when reading from config files with blank lines. Version 3.4.3 - 4/23/2011 Under Windows, allow use of the system key container for private keys. See -y on the server, and -m on the client and proxy for more details. Previously, when a client registered with a server in open group membership mode, it responded using the IP of the first network interface, which is not necessarily the interface the ANNOUNCE was received on. The client now responds with a blank IP, telling the server/proxy to use the source IP of the incoming packet as the client's IP. Enabled NO_ENCRYPTION compile flag on Windows. Version 3.4.2 - 3/29/2011 Fixed error when passing "-Y none" to the server The pidfile option (-P) for the client and proxy didn't work under Windows. It does now. Improved internal error checking. Version 3.4.1 - 2/21/2011 Fixed crash in Windows when receiving an empty file Extended the late register and late done timers to 1.5 times the old values. This gives clients a little extra time to send responses. Clients now send a COMPLETE in response to a FILEINFO for a directory or a symbolic link, elimitating an extra DONE/COMPLETE cycle. Clients may now accept a FILEINFO in lieu of a REG_CONF. This eliminates a timing issue when a REG_CONF gets lost. Version 3.4 - 12/18/2010 Added new proxy mode: response proxy Used when servers have direct multicast accessability to clients, but response aggregation is still desired. Fixed overflow bug when calculating packet rate with jumbo frames. Fixed server bug when checking maximum size of file exclusion list. Fixed bug in BSD systems where setting of DSCP/TOS value failed. Updated documentation regarding Windows support for DSCP/TOS. Version 3.3.3 - 11/26/2010 Added -Q option to server, client, and proxy to specify the DSCP (formerly TOS) in the IP header for all outgoing packets. Increased max announce time and status time from 10000 to 20000 ms. Version 3.3.2 - 10/13/2010 Added DESTDIR option to makefile to allow installs in fake root directories. Fixed overflow bug with session longer than 30 minutes. Added examples to man pages Version 3.3.1 - 7/18/2010 Added -i option to server to take list of file to send. Added -T option to server to always print timestamps. Fixed bug introduced in 3.3 where a single dropped REG_CONF results in a client not being able to register. Added makefile support for Darwin/OSX. Improved error handling. Version 3.3 - 7/8/2010 Added ability to restart failed file transfers (see server -f and -F). Removed timeout option from proxy (-o) and client (-t) The server specified status_time is used instead. Allow proxies to keep track of multiple pending messages at once. Added makefile flags to compile without encryption support. Fixed handling of snprintf return codes under Windows. Have proxies foward ABORTs after local handling. Version 3.2.1 - 6/16/2010 Under Windows, setting the root directory of a drive (c:\, d:\, etc.) as the destination directory caused errors. Fixed. When trying to send unicast to a client beind a NAT, the client would reject the transfer because the IP specified by the server doesn't match the client's IP. Clients will now always accept in unicast mode. Version 3.2 - 5/30/2010 Fixed compile bug on OSX (cleaned up signal setup) Added detection of non-multicast interfaces Added unique ID to clients and proxies. This allows for distinguising clients between different NATs that might have the same IP address. Version 3.1 - 4/6/2010 Added ability to send empty directories and symbolic links Added -l option to server to follow / not follow links Added -D option to server to specify destination file name Fixed client bug when receiving a file with the same name as an existing directory and vice versa Version 3.0 - 3/11/2010 Added SSL derived encryption. Uses RSA for host authentication and key exchange. Uses DES, Triple DES, AES-128, and AES-256 for data encryption. Ability to send multiple files or full directories at once. Clients may be listed in a file instead of the server command line. Added proxy daemon which allows: NAT traversal Aggregation of client responses Multicast tunneling Support for variable packet sizes. Removed latency levels in favor of more fine grained control. Support for source specific multicast. Protocol heavily altered to support new features. Code completely restructured and reformatted for ease of updates and support. Version 2.10.3 - 1/21/2010 Fixed ttl bug under Solaris where no value was valid Cleaned up handling of server timeout so an extra DONE request isn't sent Changed Windows sockets error messages to use proper system message strings Version 2.10.2 - 11/3/2009 The change in 2.10.1 that fixed the NAK issue caused a crash when sending or receiving empty files. This has been fixed. When specifying the list of interfaces for the client to listen on, if the interfaces are listed by name only the first was used. Bug fixed. Cleaned up more warnings on Windows Version 2.10.1 - 8/20/2009 Fix for last packet wait bug, introduced in 2.3 Fix for NAKs with block counts a multiple of the section size Fixed ttl bug - now uses actual number instead of ascii value of the number. This also results in the default ttl changing from 49 to the intended value of 1, so this may have an effect on existing apps. Thanks to Luc Tanguay for finding these bugs Cleaned up some warnings on Windows Version 2.10 - 7/16/2009 Added -B option to client and server to set UDP receive buffer size. Defaults to the old hardcoded value of 256K. Added -z option to server, which tells clients to make responses as small as possible. This minimizes backtraffic when you have a large number of receivers. Adjusted timeouts to work together better. Server now exits if invalid hosts are specified. Fixed some minor logging bugs. Version 2.9.2 - 6/20/2009 Getting the list of network interfaces is no longer manditory. If there's a problem getting the list, and you don't pass -I to the client, it just uses the clients hostname interface as before Fixed bounds checking on the server's -R and -c options, and increased the maximum valid value for -c to 60000 (60 seconds). Fixes to run properly under FreeBSD, including: Use signal(2) instead of sigset(2) for signal processing Use getifaddrs to get list of network interfaces Try a smaller value for the receive buffer if the first fails Version 2.9.1 - 6/13/2009 Allow the full 10000 receivers for closed group membership as well as open. Multiple ANNOUNCE message are now sent to accomidate the longer lists. Minor modifications to compile under OpenVMS. Fallback to listening on the client's hostname interface if interface list contains no active non-loopback interfaces. Got rid of debug message on startup on Windows boxes. Version 2.9 - 6/8/2009 After numerous complaints from Linux users, the uftpd client now listens for multicast traffic on all non-loopback interfaces by default. You can still restrict the interfaces using the -I option. The -I option for both the client and server will take interface names (ex. eth0, bge1, etc.) as well as hostnames or IPs. Broke out implementations in header files into separate source files Commoned up all UNIX-like makefiles Version 2.8.1 - 5/18/2009 Pidfile option to uftpd wasn't optional as documented. It is now. Fixed bug in reporting of total time. Was previously looking for newest end timestamp among all receivers, now only checking completed ones. Version 2.8 - 4/26/2009 Functionality change: Instead of the server sending a REG_CONF separately to each client, it sends one listing up to 300 clients, and more if required. These are sent out before each subsequent ANNOUNCE and, in closed group mode, after receiving REGISTERS from all clients. The handling of DONE_CONF messages also changed to work in a similar manner. As a result, the client side timeouts were increased to handle this. Added -c option to uftp to explicitly set client timeouts in milliseconds for both REG_CONF and DONE_CONF. Useful for when the server expects to hear from a large number (>100) of receivers over a low speed link, meaning it could take the server several seconds to process them all. If this value is less than the time for the specified latency level, it is ignored. Version 2.7.1 - 3/31/2009 Added -P option to uftpd to write the daemon's pid to a pidfile. Added latency level 4 with values 2x that of level 3. Fixed bug when sending DONE to over 300 clients. Brought man pages up to date. Changed license to GPLv3. Version 2.7 - 2/26/2009 Added -n option to uftp to prevent name lookups of clients (a similar option exists in uftpd). This happens for both open group membership, when clients register, and for closed group membership, when names are specified on the command line via -H. For the latter to take effect, -n must appear before -H on the command line. Changed name of wait variable in uftp to get around conflict on OSX/BSD, and added OSX/BSD makefile. Thanks to Jusin Venus for supplying these. Version 2.6.6 - 1/16/2009 Fixed logging output to display correctly on 64 bit systems Version 2.6.5 - 12/18/2008 Cleaned up a few more warnings Version 2.6.4 - 12/16/2008 Cleaned up some warnings, added -Wall to linux makefile Version 2.6.3 - 7/29/2008 Fixed the basename function for Windows, and made a proper makefile for Windows. Thanks to Amol Deshpande for providing these. Removed the call to basename from uftpd, since it has no effect. Version 2.6.2 - 7/28/2008 Cleaned up the code a bit so file names/paths have a consistent size, fixed bounds checking on several buffers In-code max of 10000 receivers for open group membership, 100 for closed Version 2.6.1 - 6/11/2007 Fixed wait bug in uftp Version 2.6 - 2/25/2007 Changed all "short", "long", and "long long" types to the appropriate fixed size types (16, 32, and 64, respectively) for better compatibility between 32 bit and 64 bit environments. Man pages added for Unix/Linux systems. The -I option to uftpd is now optional. It defaults to the interface assciated with the client's hostname. Version 2.5.3 - 10/27/2006 In version 2.5.1, a fix was applied to uftpd to only close a file if it was still open. But apparently, the old line of code that closes it outright wasn't removed (doh!). It's gone now. Version 2.5.2 - 10/26/2006 Bug fixes in uftpd - the -n option was actually causing the opposite of the intended effect, and forgot to check for a null value from gethostbyaddr when looking up the sender's name. Version 2.5.1 - 9/25/2006 Fix in uftpd - don't close file if already closed. On most OS's, this isn't an issue, but under MS Visual Studio it caused a crash. Thanks to Mark Leavy for catching this one. Made a similar fix in uftp. Actually, the file was never formally closed. It is now. Version 2.5 - 9/20/2006 Allowed the -I option to uftpd to take multiple parameters separated by commas. This allows you to listen for multicast traffic on multiple interfaces, and also allows you to receive data on one interface and send responses back on another. Version 2.4 - 9/16/2006 Added -L option to uftp to allow output to go to a log file. The default is to write to stderr. Modified all error routines in uftp to use logfunc. This is how error logging is currently done in uftpd. Version 2.3.1 - 7/25/2006 Fixed to allow zero length files to be transferred. Version 2.3 - 7/16/2006 Fixed handling of large files in Windows Added descriptive error messages for Windows socket errors Functionality change: uftpd now runs in a single thread. This not only resulted in a speed increase on most systems, but also gets back the functionality of receiving multiple files at once in unicast mode, which was lost in version 1.3. It is also no longer necessary to have multiple sockets open on the same port, which caused an issue where multiple instances could be running on the same port. The code for uftpd was also restructured as a result, becoming more modular. Removed -s option from uftpd, since the conditions necessitating this option no longer exist. Version 2.2 - 7/7/2006 Added -n flag to uftpd prevent name lookups of transmitter. This can eliminate issues with slow name lookups delaying registration. Version 2.1.1 - 7/5/2006 Fixed handling of -H option to uftp Version 2.1 - 6/22/2006 Made a few fixes specific to Linux machines: When calling select(3c) with a timeout, save off a copy of the timeout variable and reset it just before calling select. This gets around Linux-specific behavior which modifies the timeout parameter after the call to indicate the amount of time left to wait. Added a -s option to uftpd. Apparently, when multiple UDP sockets are open on the same port in Linux, any packet arriving on this port gets passed to ALL open sockets, regardless of whether or not the multicast destination of the packet matches one of the multicast addresses the socket is bound to. This causes a slew of "invalid txID" or "invalid function: ANNOUNCE" messages that otherwise shouldn't show up. The -s option will suppress these messages. NOTE: This option should ONLY be used on Linux boxes (or other UNIX-like OS's that exibit this behavior), as it could hide a real problem. Windows and Solaris uses should NOT use this option. Version 2.0 - 5/4/2006 UFTP now runs under Windows! It will compile under both Visual C++ 6.0 and Visual Studio .NET, and run on Windows 2000/XP. No MFC or .NET functionality was employed, only straight WIN32 calls. A few major changes under the hood made it possible to have one version of the code that compiles in both enivronments: The receiver, uftpd, now uses multiple threads instead of multiple processes. This was made necessary because Windows doesn't support the UNIX fork function, which makes an exact copy of the calling process. The logging functions in uftpd had to be modified to allow for a common interface for UNIX and Windows boxes. Changed timing routines in uftp. Previously, the gettimeofday function was used to get microsecond resolution timestamps. Since Windows doesn't have this function, calls to this function in addition to calculations of timestamp differences were hidden away behind #defines, so that UNIX boxes still call gettimeofday, and Windows boxes use QueryPerformanceCounter. Several other functions, including perror, usleep, link, were abstracted behind #defines to allow for a common interface for UNIX and Windows. The Windows Sleep function only has millisecond timing, however it still allows for proper wait times between packet transmissions. Wrote my own versions of getopt and basename, since they don't exist in Windows. When compliled for UNIX, the built in OS functions are used. Currently, the Windows version of uftpd does not put itself in the background. A Command Prompt window will remain open, even without the -d option. This was a consequence of using main instead of WinMain for the entry point to keep it consistent with UNIX. This can be worked around by calling uftpd with hidedos.exe, which is a utility program created by LanDesk. This utility can be found freely on several LanDesk related forums. Version 1.4.2 - 1/24/2006 Bug fix - missing htons() call when assigning port in uftpd. Not a big deal for big-endian machines, BAD for little-endian machines. Version 1.4.1 - 1/15/2006 Added a few missing header files Version 1.4 - 11/16/2005 Added support for large (>2GB) files On second and subsequent passes, status requests are not sent for a particular section if there were no NAKs for that section. A status request is always sent for the last section, however. Version 1.3 - 9/25/2005 Add -l flag to uftp to set a predefined latency level 1-3 (1=low, 2=medium, 3=high (default)) In prior versions, during the initial handshaking and when requesting NAK counts, the transmitter would wait 2 and 4 seconds respectively before resending a request. This is fine for a high latency link (satellite, WAN) but not a low latency link (local LAN). This setting allows for shorter waits to handle a variety of network latencies. This flag should be omitted for backward compatibility Functionality change: The parent uftpd process no longer forwards data to its children. The children now receive the data directly from the transmitter. This greatly improves high end throughput. However, doing so relies on a multicast feature (allowing multiple UDP sockets to listen on the same port) that doesn't work in unicast. Therefore, in order to retain the ability to receive files via unicast, uftpd cannot receive files via unicast and multicast at the same time. When a request for a unicast transfer is received, it is accepted only if no other files are being received. The parent uftpd then handles the file directly and will not accept any other files until it is done. Version 1.2 - 9/14/2005 Improved error handling Improved cross-platform compatibility Version 1.1 - 8/15/2005 All integers in UFTP header converted to network byte order before sending, and converted back upon receiving, allowing machines with different hardware architechtures to communicate Minor performance enhancements Version 1.0 Initial Release uftp-4.1.5/proxy_init.h0000644000076400007640000000270412250244021014053 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_INIT_H #define _PROXY_INIT_H void pre_initialize(void); void initialize(void); #endif // _PROXY_INIT_H uftp-4.1.5/proxy_upstream.c0000644000076400007640000012261712250244021014751 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include #include "win_func.h" #else #include #include #include #include #include #include #endif #include "proxy.h" #include "proxy_common.h" #include "proxy_upstream.h" #include "proxy_downstream.h" /** * Finds next open slot in the global group list. * Returns a pointer to the open slot, or NULL if none found. */ struct pr_group_list_t *find_open_slot(void) { int i; for (i = 0; i < MAXLIST; i++) { if (group_list[i].group_id == 0) { memset(&group_list[i], 0, sizeof(group_list[i])); return &group_list[i]; } } return NULL; } /** * 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 pr_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))) { log2(group->group_id, 0, "Failed to get random bytes for rand2"); send_upstream_abort(group, 0, "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)) { log2(group->group_id,0, "Failed to get random bytes for premaster"); send_upstream_abort(group, 0, "Failed to get random bytes for premaster"); return 0; } group->premaster_len = MASTER_LEN; } else { if (!get_ECDH_key(group->server_dhkey.ec, group->proxy_dhkey.ec, group->premaster, &group->premaster_len)) { log2(group->group_id,0, "Failed to calculate ECDH key"); send_upstream_abort(group, 0, "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; } /** * Read encryption related fields from an ANNOUNCE */ int read_announce_encryption(struct pr_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)) { log2(group->group_id, 0, "Keytype invalid or not supported here"); send_upstream_abort(group, 0, "Keytype invalid or not supported here"); return 0; } if (!hash_supported(encinfo->hashtype)) { log2(group->group_id, 0, "Hashtype invalid or not supported here"); send_upstream_abort(group, 0, "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)))) { log2(group->group_id, 0, "Invalid sigtype specified"); send_upstream_abort(group, 0, "Invalid sigtype specified"); return 0; } if ((keyextype != KEYEX_RSA) && (keyextype != KEYEX_ECDH_RSA) && (keyextype != KEYEX_ECDH_ECDSA)) { log0(group->group_id, 0, "Invalid keyextype specified"); send_upstream_abort(group, 0, "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_fingerprint(server_fp, server_fp_count, keys, ntohs(encinfo->keylen), group, group->src_id)) { log2(group->group_id, 0, "Failed to verify server key fingerprint"); send_upstream_abort(group,0, "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))) { log2(group->group_id, 0, "Failed to load server public key"); send_upstream_abort(group, 0, "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->proxy_privkey = privkey[i]; group->proxy_privkeylen = RSA_keylen(privkey[i].rsa); break; } } } else { if (!import_EC_key(&group->server_pubkey.ec, keys, ntohs(encinfo->keylen), 0)) { log0(group->group_id, 0, "Failed to load server public key"); send_upstream_abort(group, 0, "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->proxy_privkey = privkey[i]; group->proxy_privkeylen = ECDSA_siglen(privkey[i].ec); break; } } } if (!group->proxy_privkey.key) { log2(group->group_id, 0, "No proxy key compatible with server key"); send_upstream_abort(group,0, "No proxy key 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)) { log2(group->group_id, 0, "Failed to load server public ECDH key"); send_upstream_abort(group, 0, "Failed to load server public ECDH key"); return 0; } if (proxy_type == RESPONSE_PROXY) { if (get_EC_curve(group->server_pubkey.ec) == get_EC_curve(dhkey.ec)) { group->proxy_dhkey = dhkey; } else { log2(group->group_id, 0, "Proxy ECDH key not compatible with server key"); send_upstream_abort(group, 0, "Proxy ECDH key not compatible with server key"); return 0; } } else { group->proxy_dhkey.ec = gen_EC_key(get_EC_curve(group->server_dhkey.ec), 1, NULL); if (!group->proxy_dhkey.key) { log2(group->group_id, 0, "Failed to generate proxy ECDH key"); send_upstream_abort(group, 0, "Failed to generate proxy 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)) { log2(group->group_id, 0, "Signature verification failed"); send_upstream_abort(group, 0, "Signature verification failed"); free(sigcopy); return 0; } } else { if (!verify_ECDSA_sig(group->server_pubkey.ec, group->hashtype, packet, packetlen, sigcopy, siglen)) { log2(group->group_id, 0, "Signature verification failed"); send_upstream_abort(group, 0, "Signature verification failed"); free(sigcopy); return 0; } } free(sigcopy); } // Calculate keys if (!calculate_server_keys(group, encinfo)) { return 0; } return 1; } /** * Read in the contents of an ANNOUNCE. */ int read_announce(struct pr_group_list_t *group, unsigned char *packet, const union sockaddr_u *src, 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->version = header->version; group->group_id = ntohl(header->group_id); group->group_inst = header->group_inst; group->up_addr = *src; group->src_id = header->src_id; group->grtt = unquantize_grtt(header->grtt); group->robust = announce->robust; group->cc_type = announce->cc_type; group->gsize = unquantize_gsize(header->gsize); group->blocksize = ntohs(announce->blocksize); 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->publicmcast.sin6.sin6_family = AF_INET6; group->privatemcast.sin6.sin6_family = AF_INET6; memcpy(&group->publicmcast.sin6.sin6_addr.s6_addr, publicmcast, iplen); memcpy(&group->privatemcast.sin6.sin6_addr.s6_addr, privatemcast,iplen); group->publicmcast.sin6.sin6_port = htons(out_port); group->privatemcast.sin6.sin6_port = htons(out_port); } else { group->publicmcast.sin.sin_family = AF_INET; group->privatemcast.sin.sin_family = AF_INET; memcpy(&group->publicmcast.sin.sin_addr.s_addr, publicmcast, iplen); memcpy(&group->privatemcast.sin.sin_addr.s_addr, privatemcast, iplen); group->publicmcast.sin.sin_port = htons(out_port); group->privatemcast.sin.sin_port = htons(out_port); } if ((announce->hlen * 4U) < sizeof(struct announce_h) + (2U * iplen)) { log0(group->group_id, 0, "Rejecting ANNOUNCE from %08X: " "invalid header size", ntohl(group->src_id)); send_upstream_abort(group, 0, "Invalid header size"); return 0; } if ((announce->hlen * 4U) > sizeof(struct announce_h) + (2U * iplen)) { he = (uint8_t *)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)))) { log1(group->group_id, 0, "Rejecting ANNOUNCE from %08X: " "invalid extension size", ntohl(group->src_id)); send_upstream_abort(group, 0, "Invalid extension size"); return 0; } } } if ((encinfo != NULL) && (proxy_type != SERVER_PROXY)) { if (!read_announce_encryption(group, encinfo, packet, packetlen)) { 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->phase_expire_time, NULL); if (group->robust * group->grtt < 1.0) { add_timeval_d(&group->phase_expire_time, 1.0); } else { add_timeval_d(&group->phase_expire_time, group->robust * group->grtt); } // 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; } return 1; } /** * Inserts the proxy's public keys into an ANNOUNCE * Returns 1 on success, 0 on fail */ int insert_pubkey_in_announce(struct pr_group_list_t *group, unsigned char *packet, int packetlen) { struct announce_h *announce; struct enc_info_he *encinfo; unsigned char *keyblob, *dhkeyblob; uint16_t bloblen; unsigned int iplen; 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) + (2U * iplen)); keyblob = ((unsigned char *)encinfo + sizeof(struct enc_info_he)); dhkeyblob = keyblob + ntohs(encinfo->keylen); if ((group->keytype != KEY_NONE) && (proxy_type == CLIENT_PROXY)) { // Plug in proxy's public key for server's if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (!export_RSA_key(group->proxy_privkey.rsa, keyblob, &bloblen)) { log2(group->group_id, 0, "Error exporting proxy public key"); return 0; } } else { if (!export_EC_key(group->proxy_privkey.ec, keyblob, &bloblen)) { log2(group->group_id, 0, "Error exporting proxy public key"); return 0; } } if (bloblen != ntohs(encinfo->keylen)) { log2(group->group_id, 0, "Incorrect exported proxy key size"); return 0; } if ((group->keyextype == KEYEX_ECDH_ECDSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (!export_EC_key(group->proxy_dhkey.ec, dhkeyblob, &bloblen)) { log2(group->group_id,0,"Error exporting proxy ECDH public key"); return 0; } if (bloblen != ntohs(encinfo->dhlen)) { log2(group->group_id, 0, "Incorrect exported proxy ECDH key size"); return 0; } } } return 1; } /** * Handles an incoming ANNOUNCE message from a server. * Sets up encryption if specified and forwards message. */ void handle_announce(struct pr_group_list_t *group, const union sockaddr_u *src, unsigned char *packet, unsigned packetlen) { struct uftp_h *header; struct announce_h *announce; char pubname[INET6_ADDRSTRLEN], privname[INET6_ADDRSTRLEN]; int rval; header = (struct uftp_h *)packet; announce = (struct announce_h *)(packet + sizeof(struct uftp_h)); if ((packetlen < sizeof(struct uftp_h) + (announce->hlen * 4)) || ((announce->hlen * 4) < sizeof(struct announce_h))) { log1(group->group_id, 0, "Rejecting ANNOUNCE from %08X: " "invalid message size", ntohl(header->src_id)); return; } if (group == NULL) { if ((group = find_open_slot()) == NULL ) { log1(ntohl(header->group_id), 0, "Error: maximum number of " "incoming files exceeded: %d\n", MAXLIST); return; } if (!read_announce(group, packet, src, packetlen)) { return; } if ((rval = getnameinfo((struct sockaddr *)&group->publicmcast, family_len(group->publicmcast), pubname, sizeof(pubname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } if ((rval = getnameinfo((struct sockaddr *)&group->privatemcast, family_len(group->privatemcast), privname, sizeof(privname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log0(group->group_id, 0, "Received request from %08X", ntohl(group->src_id)); log1(group->group_id, 0, "Using public multicast address %s", pubname); log1(group->group_id, 0, "Using private multicast address %s",privname); if (!addr_blank(&group->privatemcast) && (proxy_type != CLIENT_PROXY)) { if (server_fp_count) { if (!is_multicast(&group->privatemcast, 1)) { log2(group->group_id, 0, "Invalid source specific multicast address: %s", privname); send_upstream_abort(group, 0, "Invalid source specific multicast address"); return; } } else { if (!is_multicast(&group->privatemcast, 0)) { log2(group->group_id, 0, "Invalid multicast address: %s", privname); send_upstream_abort(group, 0, "Invalid multicast address"); return; } } if (!multicast_join(listener, group->group_id, &group->privatemcast, m_interface, interface_count, server_fp, server_fp_count)) { send_upstream_abort(group, 0, "Error joining multicast group"); return; } group->multi_join = 1; } group->phase = PR_PHASE_REGISTERED; } if (insert_pubkey_in_announce(group, packet, packetlen)) { forward_message(group, src, packet, packetlen); } } /** * Handles in incoming REG_CONF from a server when encryption is enabled. * Upon receiving this message, mark all clients listed as having received. * If we got a KEYINFO from the server, send a KEYINFO to all marked clients. */ void handle_regconf(struct pr_group_list_t *group, const unsigned char *message, unsigned meslen) { const struct regconf_h *regconf; const uint32_t *addrlist; int hostidx, idx, addrcnt; struct pr_destinfo_t *dest; regconf = (const struct regconf_h *)message; addrlist = (const uint32_t *)(message + (regconf->hlen * 4)); addrcnt = (meslen - (regconf->hlen * 4)) / 4; if ((meslen < (regconf->hlen * 4U)) || ((regconf->hlen * 4U) < sizeof(struct regconf_h))) { log1(group->group_id, 0, "Rejecting REG_CONF from server: invalid message size"); return; } log2(group->group_id, 0, "Received REG_CONF"); for (idx = 0; idx < addrcnt; idx++) { hostidx = find_client(group, addrlist[idx]); if (hostidx != -1) { dest = &group->destinfo[hostidx]; log2(group->group_id, 0, " for %s", dest->name); if (dest->state != PR_CLIENT_READY) { dest->state = PR_CLIENT_CONF; } } } if (group->phase == PR_PHASE_READY) { send_keyinfo(group, addrlist, addrcnt); } set_timeout(group, 0, 0); } /** * Handles an incoming KEYINFO message from a server. * Expected in response to a REGISTER when encryption is enabled. The proxy * itself should be specified, not any clients behind it. */ void handle_keyinfo(struct pr_group_list_t *group, unsigned char *message, unsigned meslen, uint32_t src_id) { struct keyinfo_h *keyinfo_hdr; struct destkey *keylist; unsigned explen, declen; int i, keyidx, len, keycount, unauth_keytype, unauth_keylen, unauth_ivlen; 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)); keycount = (meslen - (keyinfo_hdr->hlen * 4)) / sizeof(struct destkey); if ((meslen < (keyinfo_hdr->hlen * 4U)) || ((keyinfo_hdr->hlen * 4U) < sizeof(struct keyinfo_h))) { log1(group->group_id, 0, "Rejecting KEYINFO from server: invalid message size"); return; } if (group->keytype == KEY_NONE) { log1(group->group_id, 0, "Rejecting KEYINFO from server: encryption not enabled"); return; } for (i = 0, keyidx = -1; (i < keycount) && (keyidx == -1); i++) { if (uid == keylist[i].dest_id) { keyidx = i; break; } } // 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) { log2(group->group_id, 0, "Received KEYINFO"); if (group->phase != PR_PHASE_REGISTERED) { // We already got the KEYINFO, so no need to reprocess. // Just resend the INFO_ACK and reset the timeout send_keyinfo_ack(group); 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)) { log2(group->group_id, 0, "Decrypt failed for group master"); send_upstream_abort(group, 0, "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 = PR_PHASE_READY; // Respond to server, then send any pending REG_CONFs as KEYINFO send_keyinfo_ack(group); send_keyinfo(group, NULL, 0); } } /** * Sends a REGISTER to the server for all pending clients. */ void send_register(struct pr_group_list_t *group, int pendidx) { struct uftp_h *header; struct register_h *reg; unsigned char *buf, *keydata; uint32_t *addrlist; unsigned int len, meslen, destcount; struct timeval now, send_time; 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 (!RSA_encrypt(group->server_pubkey.rsa, group->premaster, group->premaster_len, keydata, &len)) { log2(group->group_id, 0, "Error encrypting premaster secret"); send_upstream_abort(group, 0, "Error encrypting premaster secret"); free(buf); return; } } else { uint16_t keylen; if (!export_EC_key(group->proxy_dhkey.ec, keydata, &keylen)) { log2(group->group_id, 0, "Error exporting ECDH public key"); send_upstream_abort(group,0, "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->pending[pendidx].rx_tstamp) <= 0) { send_time = group->pending[pendidx].tstamp; } else { send_time = add_timeval(group->pending[pendidx].tstamp, diff_timeval(now, group->pending[pendidx].rx_tstamp)); } reg->tstamp_sec = htonl((uint32_t)send_time.tv_sec); reg->tstamp_usec = htonl((uint32_t)send_time.tv_usec); addrlist = (uint32_t *)(keydata + len); reg->hlen = (sizeof(struct register_h) + len) / 4; destcount = load_pending(group, pendidx, REGISTER, addrlist, max_msg_dest(group, REGISTER, reg->hlen * 4)); meslen = sizeof(struct uftp_h) + (reg->hlen * 4) + (destcount * 4); if (nb_sendto(listener, buf, meslen, 0, (struct sockaddr *)&group->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, 0, "Error sending REGISTER"); } else { log2(group->group_id, 0, "REGISTER sent"); } if (group->client_auth) { send_clientkey(group); } set_timeout(group, 1, 0); free(buf); } /** * Sends a CLIENT_KEY message to the server if requested. */ void send_clientkey(struct pr_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; uint16_t bloblen; int verifylen; 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, -1, &verifylen, 0); if (!verifydata) { log2(group->group_id, 0, "Error getting verify data"); send_upstream_abort(group, 0, "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->proxy_privkey.rsa, keyblob, &bloblen)) { log2(group->group_id, 0, "Error exporting public key"); send_upstream_abort(group, 0, "Error exporting public key"); goto end; } verify = keyblob + bloblen; if (!create_RSA_sig(group->proxy_privkey.rsa, group->hashtype, verifydata, verifylen, verify, &siglen) || (siglen > group->proxy_privkeylen)) { log2(group->group_id, 0, "Error signing verify data"); send_upstream_abort(group, 0, "Error signing verify data"); goto end; } } else { if (!export_EC_key(group->proxy_privkey.ec, keyblob, &bloblen)) { log2(group->group_id, 0, "Error exporting public key"); send_upstream_abort(group, 0, "Error exporting public key"); goto end; } verify = keyblob + bloblen; if (!create_ECDSA_sig(group->proxy_privkey.ec, group->hashtype, verifydata, verifylen, verify, &siglen)) { log2(group->group_id, 0, "Error signing verify data"); send_upstream_abort(group, 0, "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->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, 0, "Error sending CLIENT_KEY"); } else { log2(group->group_id, 0, "CLIENT_KEY sent"); } end: free(verifydata); free(buf); } /** * Sends an KEYINFO_ACK to the server in response to a KEYINFO */ void send_keyinfo_ack(struct pr_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, len, enclen; 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, -1, &verifylen, 1); if (!verifydata) { log2(group->group_id, 0, "Error getting verify data"); send_upstream_abort(group, 0, "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->proxy_privkey, group->proxy_privkeylen)) { log2(group->group_id, 0, "Error encrypting KEYINFO_ACK"); free(buf); return; } payloadlen = enclen + sizeof(struct uftp_h); if (nb_sendto(listener, encrypted, payloadlen, 0, (struct sockaddr *)&group->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, 0, "Error sending KEYINFO_ACK"); } else { log2(group->group_id, 0, "KEYINFO_ACK sent"); } set_timeout(group, 0, 0); free(encrypted); free(buf); } /** * Sends a FILEINFO_ACK to the server for all pending clients */ void send_fileinfo_ack(struct pr_group_list_t *group, int pendidx) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct fileinfoack_h *fileinfo_ack; struct pr_pending_info_t *pending; unsigned int payloadlen; int destcount, enclen; uint32_t *addrlist; struct timeval now, send_time; buf = safe_calloc(MAXMTU, 1); pending = &group->pending[pendidx]; header = (struct uftp_h *)buf; fileinfo_ack = (struct fileinfoack_h *)(buf + sizeof(struct uftp_h)); addrlist =(uint32_t *)((char *)fileinfo_ack + sizeof(struct fileinfoack_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(pending->file_id); if (pending->partial) { fileinfo_ack->flags |= FLAG_PARTIAL; } gettimeofday(&now, NULL); if (cmptimestamp(now, group->pending[pendidx].rx_tstamp) <= 0) { send_time = group->pending[pendidx].tstamp; } else { send_time = add_timeval(group->pending[pendidx].tstamp, diff_timeval(now, group->pending[pendidx].rx_tstamp)); } fileinfo_ack->tstamp_sec = htonl((uint32_t)send_time.tv_sec); fileinfo_ack->tstamp_usec = htonl((uint32_t)send_time.tv_usec); destcount = load_pending(group, pendidx, FILEINFO_ACK, addrlist, max_msg_dest(group, FILEINFO_ACK, fileinfo_ack->hlen * 4)); payloadlen += destcount * sizeof(uint32_t); 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->proxy_privkey, group->proxy_privkeylen)) { log2(group->group_id, pending->file_id, "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->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, pending->file_id, "Error sending FILEINFO_ACK"); } else { log2(group->group_id, pending->file_id, "FILEINFO_ACK sent"); } set_timeout(group, 1, 0); free(encrypted); free(buf); } /** * Counts the pending naks for the given group */ int count_naks(struct pr_group_list_t *group, int pendidx) { unsigned nak_count, i; for (nak_count = 0, i = 0; i < group->blocksize * 8; i++) { if ((group->pending[pendidx].naklist[i >> 3] & (1 << (i & 7))) != 0) { nak_count++; } } // Highly verbose debugging -- print aggregate NAKs before sending if (log_level >= 5) { for (i = 0; i < group->blocksize; i++) { sclog5("%02X ", group->pending[pendidx].naklist[i]); if (i % 25 == 24) slog5(""); } slog5(""); } return nak_count; } /** * Sends a STATUS to the server for all pending clients. */ void send_status(struct pr_group_list_t *group, int pendidx) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct status_h *status; unsigned char *sent_naks; struct pr_pending_info_t *pending; struct pr_destinfo_t *dest; int hostidx, payloadlen, enclen, nak_count; buf = safe_calloc(MAXMTU, 1); pending = &group->pending[pendidx]; // Since a STATUS doesn't contain a host list, we do this simplified // cleanup instead of calling load_pending for (hostidx = 0; hostidx < group->destcount; hostidx++) { dest = &group->destinfo[hostidx]; if (dest->pending == pendidx) { dest->pending = -1; } } group->pending[pendidx].count = 0; group->pending[pendidx].msg = 0; header = (struct uftp_h *)buf; status = (struct status_h *)(buf + sizeof(struct uftp_h)); nak_count = count_naks(group, pendidx); set_uftp_header(header, STATUS, group); status->func = STATUS; status->hlen = sizeof(struct status_h) / 4; status->file_id = htons(pending->file_id); status->section = htons(pending->section); payloadlen = group->blocksize; sent_naks = (unsigned char *)status + sizeof(struct status_h); memcpy(sent_naks, pending->naklist, payloadlen); memset(pending->naklist, 0, payloadlen); payloadlen += sizeof(struct status_h); 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->proxy_privkey, group->proxy_privkeylen)) { log2(group->group_id, pending->file_id, "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->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, pending->file_id, "Error sending STATUS"); } else { log2(group->group_id, pending->file_id, "Sent %d NAKs for section %d", nak_count, pending->section); } set_timeout(group, 1, 0); free(buf); free(encrypted); } /** * Sends a COMPLETE to the server for all pending clients. */ void send_complete(struct pr_group_list_t *group, int pendidx) { unsigned char *buf, *encrypted, *outpacket; struct uftp_h *header; struct complete_h *complete; uint32_t *addrlist; struct pr_pending_info_t *pending; int payloadlen, destcount, enclen; buf = safe_calloc(MAXMTU, 1); pending = &group->pending[pendidx]; header = (struct uftp_h *)buf; complete = (struct complete_h *)(buf + sizeof(struct uftp_h)); addrlist = (uint32_t *)((char *)complete + sizeof(struct complete_h)); set_uftp_header(header, COMPLETE, group); complete->func = COMPLETE; complete->hlen = sizeof(struct complete_h) / 4; complete->file_id = htons(pending->file_id); complete->status = pending->comp_status; destcount = load_pending(group, pendidx, COMPLETE, addrlist, max_msg_dest(group, COMPLETE, complete->hlen * 4)); payloadlen = sizeof(struct complete_h) + (destcount * sizeof(uint32_t)); 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->proxy_privkey, group->proxy_privkeylen)) { log2(group->group_id, pending->file_id, "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->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, pending->file_id, "Error sending COMPLETE"); } else { log2(group->group_id, pending->file_id, "Sent COMPLETE"); } set_timeout(group, 1, 0); free(buf); free(encrypted); } uftp-4.1.5/client_common.c0000644000076400007640000011460212250244021014471 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #include #include #include #endif #include "client.h" #include "client_common.h" /** * Look for a given group in the global group list * Returns a pointer to the group in the list, or NULL if not found */ struct group_list_t *find_group(uint32_t group_id, uint8_t group_inst) { int i; for (i = 0; i < MAXLIST; i++) { if ((group_list[i].group_id == group_id) && (group_list[i].group_inst == group_inst)) { return &group_list[i]; } } return NULL; } /** * Looks for the uid in a list of addresses. * Returns 1 if found, 0 if not found */ int uid_in_list(const uint32_t *addrlist, int size) { int i; for (i = 0; i < size; i++) { if (addrlist[i] == 0) { return 0; } if (uid == addrlist[i]) { return 1; } } return 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); } } log1(group->group_id, 0, "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) { syserror(group->group_id, 0, "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) { log0(group->group_id, 0, "Failed to read header for restart file"); goto err1; } if (rval != sizeof(struct client_restart_t)) { log0(group->group_id, 0, "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) { log0(group->group_id,0, "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) { log0(group->group_id, 0, "Failed to read section_done list for restart file"); goto err3; } } close(fd); unlink(restart_name); group->restartinfo = restart; log1(group->group_id, 0, "Reading restart file done"); return; err3: free(restart->section_done); err2: free(restart->naklist); err1: free(restart); close(fd); } /** * 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; } log1(group->group_id, 0, "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) { syserror(group->group_id, 0, "Failed to create restart file"); return; } if (file_write(fd, &restart, sizeof(restart)) == -1) { log0(group->group_id, 0, "Failed to write header for restart file"); goto errexit; } if (fileinfo->blocks && fileinfo->naklist) { if (file_write(fd, fileinfo->naklist, fileinfo->blocks) == -1) { log0(group->group_id, 0, "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) { log0(group->group_id, 0, "Failed to write section_done list for restart file"); goto errexit; } } close(fd); return; errexit: close(fd); unlink(restart_name); } /** * Checks to see if the multicast address used for the given group list member * is also being used by either another member or the public address list */ int other_mcast_users(struct group_list_t *group) { int i; for (i = 0; i < pub_multi_count; i++) { if (!memcmp(&group->multi, &pub_multi[i], sizeof(union sockaddr_u))) { return 1; } } for (i = 0; i < MAXLIST; i++) { if (group_list[i].group_id == 0) { return 0; } if ((&group_list[i] != group) && (!memcmp(&group->multi, &group_list[i].multi, sizeof(union sockaddr_u)))) { return 1; } } return 0; } /** * 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) { clog2(group->group_id, group->file_id, "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); log0(group->group_id, group->file_id, "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); log0(group->group_id, group->file_id, "Error running script: (%d) %s", GetLastError(), errbuf); } } } #else { pid_t pid; if ((pid = fork()) == -1) { syserror(0, 0, "fork failed"); } else if (pid == 0) { close(listener); close(1); close(2); execv(postreceive, params); syserror(0, 0, "exec failed"); exit(1); } } #endif free(params); } /** * Run the postreceive script on a received file */ void run_postreceive(struct group_list_t *group, char *file) { char *files[] = { file }; run_postreceive_multi(group, files, 1); } /** * Clean up a group list entry. Close the file if open, * free malloc'ed structures, drop the multicast group * (if no one else is using it) and free the slot. */ void file_cleanup(struct group_list_t *group, int abort_session) { if (group->fileinfo.fd >= 0) { log2(group->group_id, group->file_id, "starting file close"); close(group->fileinfo.fd); log2(group->group_id, group->file_id, "done file close"); group->fileinfo.fd = -1; if (abort_session && !strcmp(tempdir, "")) { if (tempfile) { unlink(group->fileinfo.temppath); } else { unlink(group->fileinfo.filepath); } } else { if (tempfile) { move_to_backup(group); if (rename(group->fileinfo.temppath, group->fileinfo.filepath) == -1) { syserror(group->group_id, group->file_id, "Couldn't rename from %s to %s", group->fileinfo.temppath,group->fileinfo.filepath); } } if (group->fileinfo.tstamp) { utim_buf utbuf; utbuf.actime = group->fileinfo.tstamp; utbuf.modtime = group->fileinfo.tstamp; if (utime(group->fileinfo.filepath, &utbuf) == -1) { syserror(group->group_id, group->file_id, "utime failed"); } } } } if (abort_session || (group->file_id == 0)) { if (!addr_blank(&group->multi) && !other_mcast_users(group) && group->multi_join) { if (server_count > 0) { multicast_leave(listener, group->group_id, &group->multi, m_interface, interface_count, server_keys,server_count); if (has_proxy) { multicast_leave(listener, group->group_id, &group->multi, m_interface, interface_count, &proxy_info, 1); } } else { multicast_leave(listener, group->group_id, &group->multi, m_interface, interface_count, NULL, 0); } } if (group->server_pubkey.key) { if (group->keyextype == KEYEX_ECDH_ECDSA) { free_EC_key(group->server_pubkey.ec); } else { free_RSA_key(group->server_pubkey.rsa); } } if (group->server_dhkey.key) { free_EC_key(group->server_dhkey.ec); free_EC_key(group->client_dhkey.ec); } if (group->restartinfo && (strcmp(group->restartinfo->name, ""))) { // We have unused restart info from the last run. // Chalk this up as a loss and delete the data file char filepath[MAXPATHNAME]; snprintf(filepath, sizeof(filepath), "%s%c_group_%08X%c%s", tempdir, PATH_SEP, group->group_id, PATH_SEP, group->restartinfo->name); unlink(filepath); } if (abort_session) { write_restart_file(group); } free(group->loss_history); free(group->fileinfo.naklist); free(group->fileinfo.section_done); if (group->restartinfo) { free(group->restartinfo->naklist); free(group->restartinfo->section_done); free(group->restartinfo); } memset(group, 0, sizeof(struct group_list_t)); } else { // Don't clear the file_id in case we need to respond to late DONEs if (!strcmp(tempdir, "")) { run_postreceive(group, group->fileinfo.filepath); } group->phase = PHASE_MIDGROUP; set_timeout(group, 0); free(group->fileinfo.naklist); free(group->fileinfo.section_done); group->fileinfo.naklist = NULL; group->fileinfo.section_done = NULL; } } /** * Initializes the uftp header of an outgoing packet */ void set_uftp_header(struct uftp_h *header, int func, struct group_list_t *group) { header->version = group->version; header->func = func; header->seq = htons(group->send_seq++); header->group_id = htonl(group->group_id); header->group_inst = group->group_inst; header->src_id = uid; } /** * Sets the timeout time for a given group list member */ void set_timeout(struct group_list_t *group, int rescale) { if (!rescale) { gettimeofday(&group->start_timeout_time, NULL); } group->timeout_time = group->start_timeout_time; switch (group->phase) { case PHASE_REGISTERED: add_timeval_d(&group->timeout_time, 4 * group->grtt); break; case PHASE_RECEIVING: case PHASE_MIDGROUP: if (group->robust * group->grtt < 1.0) { add_timeval_d(&group->timeout_time, 1.0); } else { add_timeval_d(&group->timeout_time, group->robust * group->grtt); } break; case PHASE_COMPLETE: add_timeval_d(&group->timeout_time, 4 * group->grtt); break; } } /** * 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)) { log0(group->group_id, group->file_id, "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) { sockerror(group->group_id, group->file_id, "Error sending ABORT"); } file_cleanup(group, 1); free(buf); free(encrypted); } /** * 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))) { log1(group->group_id, group->file_id, "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) { log0(group->group_id, group->file_id, "Transfer aborted by server: %s", abort_hdr->message); file_cleanup(group, 1); } } /** * Sends a KEY_REQ message to the proxy specified as the reply proxy */ void send_key_req() { unsigned char *packet; struct uftp_h *header; struct key_req_h *keyreq; union sockaddr_u proxyaddr; char addrname[INET6_ADDRSTRLEN]; int meslen, rval; packet = safe_calloc(sizeof(struct uftp_h) + sizeof(struct key_req_h), 1); header = (struct uftp_h *)packet; keyreq = (struct key_req_h *)(packet + sizeof(struct uftp_h)); header->version = UFTP_VER_NUM; header->func = KEY_REQ; header->src_id = uid; keyreq->func = KEY_REQ; keyreq->hlen = sizeof(struct key_req_h) / 4; meslen = sizeof(struct uftp_h) + sizeof(struct key_req_h); proxyaddr = proxy_info.addr; if (nb_sendto(listener, packet, meslen, 0, (struct sockaddr *)&proxyaddr, family_len(proxyaddr)) == SOCKET_ERROR) { sockerror(0, 0, "Error sending KEY_REQ"); } else { if ((rval = getnameinfo((struct sockaddr *)&proxyaddr, family_len(proxyaddr), addrname, sizeof(addrname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Sent KEY_REQ to %s:%s", addrname, portname); } free(packet); gettimeofday(&next_keyreq_time, NULL); next_keyreq_time.tv_sec += KEY_REQ_INT; } /** * Process a PROXY_KEY message */ void handle_proxy_key(const union sockaddr_u *src, unsigned char *message, unsigned meslen) { struct proxy_key_h *proxykey; unsigned char *keyblob, *dhblob, *sig; unsigned char fingerprint[HMAC_LEN]; unsigned int fplen, keylen, dhlen, siglen; char addrname[INET6_ADDRSTRLEN]; int rval; proxykey = (struct proxy_key_h *)message; if (meslen < (proxykey->hlen * 4U) || ((proxykey->hlen * 4U) < sizeof(struct proxy_key_h) + ntohs(proxykey->bloblen) + ntohs(proxykey->dhlen) + ntohs(proxykey->siglen))) { log2(0, 0, "Rejecting PROXY_KEY: invalid message size"); return; } if ((rval = getnameinfo((const struct sockaddr *)src, family_len(*src), addrname, sizeof(addrname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Received PROXY_KEY from %s", addrname); if (!has_proxy) { log2(0, 0, "No reply proxy specified"); return; } if (!addr_equal(&proxy_info.addr, src)) { log2(0, 0, "PROXY_KEY not from specified reply proxy"); return; } keyblob = (unsigned char *)proxykey + sizeof(struct proxy_key_h); keylen = ntohs(proxykey->bloblen); dhblob = keyblob + keylen; dhlen = ntohs(proxykey->dhlen); sig = dhblob + dhlen; siglen = ntohs(proxykey->siglen); if (keyblob[0] == KEYBLOB_RSA) { if (!import_RSA_key(&proxy_pubkey.rsa, keyblob, keylen)) { log0(0, 0, "Failed to import public key from PROXY_KEY"); return; } if (proxy_info.has_fingerprint) { hash(HASH_SHA1, keyblob, keylen, fingerprint, &fplen); if (memcmp(proxy_info.fingerprint, fingerprint, fplen)) { log0(0, 0, "Failed to verify PROXY_KEY fingerprint"); free_RSA_key(proxy_pubkey.rsa); return; } } if (!verify_RSA_sig(proxy_pubkey.rsa, HASH_SHA1, (unsigned char *)&proxykey->nonce, sizeof(proxykey->nonce), sig, siglen)) { log0(0, 0, "Failed to verify PROXY_KEY signature"); free_RSA_key(proxy_pubkey.rsa); return; } } else { if (!import_EC_key(&proxy_pubkey.ec, keyblob, keylen, 0)) { log0(0, 0, "Failed to import public key from PROXY_KEY"); return; } if (proxy_info.has_fingerprint) { hash(HASH_SHA1, keyblob, keylen, fingerprint, &fplen); if (memcmp(proxy_info.fingerprint, fingerprint, fplen)) { log0(0, 0, "Failed to verify PROXY_KEY fingerprint"); free_RSA_key(proxy_pubkey.rsa); return; } } if (!verify_ECDSA_sig(proxy_pubkey.ec, HASH_SHA1, (unsigned char *)&proxykey->nonce, sizeof(proxykey->nonce), sig, siglen)) { log0(0, 0, "Failed to verify PROXY_KEY signature"); free_RSA_key(proxy_pubkey.rsa); return; } } if (dhlen) { if (!import_EC_key(&proxy_dhkey.ec, dhblob, dhlen, 1)) { log0(0, 0, "Failed to import ECDH public key from PROXY_KEY"); return; } } } /** * 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) { syserror(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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)) { syserror(group->group_id, group->file_id, "Failed to read directory %s", path); } closedir(dir); #endif if (rmdir(path) == -1) { syserror(group->group_id, group->file_id, "Failed remove directory %s", path); } } } /** * 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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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); log0(group->group_id, group->file_id, "Couldn't rename from %s to %s, deleting: (%d): %s", group->fileinfo.filepath, backup_file, GetLastError(), errbuf); clear_path(group->fileinfo.filepath, group); } else { log1(group->group_id, group->file_id, "Backed up existing file to %s", backup_file); } #else if (rename(group->fileinfo.filepath, backup_file) == -1) { syserror(group->group_id, group->file_id, "Couldn't rename from %s to %s, deleting", group->fileinfo.filepath, backup_file); clear_path(group->fileinfo.filepath, group); } else { log1(group->group_id, group->file_id, "Backed up existing file to %s", backup_file); } #endif } /** * 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) { log1(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "Failed to delete path element %s", dir); rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { syserror(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "Failed to create path element %s", dir); rval = 0; goto end; } } rval = 1; end: free(dir); free(base); return rval; } /** * Updates the group's loss history * * Packets older than MAXMISORDER sequence numbers don't change the loss * history, and packets aren't considered lost unless the sequence number is * more than MAXMISORDER sequence numbers old. Works under the assumption * that no more than 32K packets in a row get lost. */ void update_loss_history(struct group_list_t *group, uint16_t txseq, int size) { uint16_t i, count; uint32_t i_long; int tdiff, bytes, avgbytes, rate; struct timeval tvdiff; int grtt_usec; group->loss_history[txseq].found = 1; gettimeofday(&group->loss_history[txseq].t, NULL); if (group->multi.ss.ss_family == AF_INET6) { group->loss_history[txseq].size = size + 8 + 40; } else { group->loss_history[txseq].size = size + 8 + 20; } if ((int16_t)(txseq - group->max_txseq) > 0) { log4(group->group_id, 0, "Got seq %d, max was %d", txseq, group->max_txseq); grtt_usec = (int)(group->grtt * 1000000); if (txseq < group->max_txseq) { log5(group->group_id, 0, "increasing seq_wrap, txseq=%u, maxseq=%u", txseq, group->max_txseq); group->seq_wrap++; } // First set nominal arrival times of missed packets for (i = group->max_txseq + 1; i != txseq; i++) { tdiff = (int)diff_usec(group->loss_history[txseq].t, group->loss_history[group->max_txseq].t) * ((i - group->max_txseq) / (txseq - group->max_txseq)); tvdiff.tv_sec = 0; tvdiff.tv_usec = tdiff; while (tvdiff.tv_usec >= 1000000) { tvdiff.tv_usec -= 1000000; tvdiff.tv_sec++; } group->loss_history[i].found = 0; group->loss_history[i].t = add_timeval(group->loss_history[group->max_txseq].t,tvdiff); } // Then check for missed packets up to MAXMISORDER less than the current // Don't do this part unless we have at least MAXMISORDER packets // TODO: address issue of start_txseq being within MAXMISORDER sequence // numbers from the maximum if (group->seq_wrap ||((uint16_t)(group->max_txseq - group->start_txseq) >= MAXMISORDER)) { for (i = group->max_txseq - MAXMISORDER; i != (uint16_t)(txseq - MAXMISORDER); i++) { if (!group->loss_history[i].found && ((diff_usec(group->loss_history[i].t, group->loss_events[0].t) > grtt_usec) || group->slowstart)) { log4(group->group_id, 0, "Seq %d starts new loss event", i); // Found a new loss event if (i < group->max_txseq - MAXMISORDER) { log5(group->group_id, 0, "wrap check, i=%u, maxseq=%u", i, group->max_txseq); i_long = ((group->seq_wrap - 1) << 16) | i; } else { i_long = (group->seq_wrap << 16) | i; } if (group->slowstart) { group->slowstart = 0; // Initialize loss history count = group->max_txseq; bytes = 0; while ((count != group->start_txseq) && (diff_usec(group->loss_history[i].t, group->loss_history[count].t) < grtt_usec)) { bytes += group->loss_history[count--].size; } rate = (int)(bytes / group->grtt); log4(group->group_id, 0, "End slowstart, calculated " "rate = %d", rate); avgbytes= bytes / ((int16_t)(group->max_txseq - count)); group->loss_events[0].len = (group->rtt != 0) ? (int)(0 + pow((rate * group->rtt) / (sqrt(1.5) * 8 * avgbytes), 2)) : (int)(0 + pow((rate * group->grtt) / (sqrt(1.5) * 8 * avgbytes), 2)); log4(group->group_id, 0, "Calculated prior " "event len = %d (rtt=%f, avgbytes=%d)", group->loss_events[0].len, group->rtt,avgbytes); } else { group->loss_events[0].len = i_long - group->loss_events[0].start_seq; log4(group->group_id, 0, "Prior event length = %d " "(i=%u, start=%u)", group->loss_events[0].len, i_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 = i_long; group->loss_events[0].len = 0; group->loss_events[0].t = group->loss_history[i].t; } } } group->max_txseq = txseq; } group->loss_events[0].len = ((group->seq_wrap << 16) | group->max_txseq) - group->loss_events[0].start_seq; log5(group->group_id, 0, "current cc len = %d", group->loss_events[0].len); log5(group->group_id, 0, "seq_wrap=%d, max_txseq=%u, start_seq=%u", group->seq_wrap, group->max_txseq, group->loss_events[0].start_seq); } /** * Calculates and returns the loss event rate * TODO: add history discounting */ double loss_event_rate(struct group_list_t *group) { double weights[8] = { 1.0, 1.0, 1.0, 1.0, 0.8, 0.6, 0.4, 0.2 }; double loss_sum_cur, loss_sum_no_cur, weight_sum; int i; if (group->slowstart) { return 0.0; } loss_sum_cur = 0; loss_sum_no_cur = 0; weight_sum = 0; for (i = 0; i < 8; i++) { log5(group->group_id, 0, "loss_events[%d].len=%d", i, group->loss_events[i].len); if (group->loss_events[i].len != 0) { loss_sum_cur += group->loss_events[i].len * weights[i]; weight_sum += weights[i]; } } for (i = 1; i < 9; i++) { if (group->loss_events[i].len == 0) break; loss_sum_no_cur += group->loss_events[i].len * weights[i - 1]; } log5(group->group_id, 0, "cur_sum=%f, cur_no_sum=%f, weight_sum=%f", loss_sum_cur, loss_sum_no_cur, weight_sum); // Return inverse of larger average if (loss_sum_no_cur > loss_sum_cur) { return weight_sum / loss_sum_no_cur; } else { return weight_sum / loss_sum_cur; } } /** * Returns the current congestion control rate in bytes / second. * As specified in RFC 4654 */ unsigned current_cc_rate(struct group_list_t *group) { double p, rtt; int i, bytes, thresh; if (group->rtt != 0.0) { rtt = group->rtt; } else { rtt = group->grtt; } p = loss_event_rate(group); if (p == 0.0) { thresh = (int)(group->grtt * 1000000 * 4); bytes = 0; i = group->max_txseq; while ((i != group->start_txseq) && (diff_usec(group->loss_history[group->max_txseq].t, group->loss_history[i].t) < thresh)) { bytes += group->loss_history[i--].size; } return (unsigned)(2.0 * bytes / (4.0 * group->grtt)); } else { log5(group->group_id, 0, "getting cc rate, p=%f, rtt=%f", p, rtt); return (unsigned)(group->datapacketsize / (rtt * (sqrt(p * 2.0 / 3.0) + (12 * sqrt(p * 3.0 / 8.0) * p * (1 + (32 * p * p)))))); } } uftp-4.1.5/client_config.c0000644000076400007640000004335612250244021014455 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include "win_func.h" #else // if WINDOWS #include #include #include #include #endif #include "client.h" #include "client_config.h" /** * Global command line values and sockets */ SOCKET listener; char tempdir[MAXDIRNAME], destdir[MAXDIR][MAXDIRNAME]; char pidfile[MAXPATHNAME]; char keyfile[MAXLIST][MAXPATHNAME], keyinfo[MAXLIST][MAXPATHNAME]; char backupdir[MAXDIR][MAXDIRNAME]; int debug, encrypted_only, dscp, destdircnt, tempfile, keyinfo_count; int interface_count, pub_multi_count, keyfile_count, rcvbuf, backupcnt; char postreceive[MAXPATHNAME], portname[PORTNAME_LEN]; int port, move_individual; uint32_t uid; union sockaddr_u hb_hosts[MAXLIST]; struct iflist m_interface[MAX_INTERFACES]; union sockaddr_u pub_multi[MAX_INTERFACES]; struct group_list_t group_list[MAXLIST]; struct fp_list_t server_keys[MAXLIST]; struct iflist ifl[MAX_INTERFACES]; struct timeval next_keyreq_time, next_hb_time; int ifl_len, server_count, key_count, has_proxy, sys_keys, priority; int hbhost_count, hb_interval; union key_t privkey[MAXLIST]; int privkey_type[MAXLIST]; struct fp_list_t proxy_info; union key_t proxy_pubkey, proxy_dhkey; int proxy_pubkeytype; extern char *optarg; extern int optind; /** * Adds a server and its fingerprint to the list of approved servers */ void add_server_by_name(const char *server, const char *ip, const char *fingerprint) { struct addrinfo ai_hints, *ai_rval; uint32_t server_uid; int rval; server_uid = strtoul(server, NULL, 16); if ((server_uid == 0xffffffff) || (server_uid == 0)) { fprintf(stderr, "Invalid server UID %s\n", server); exit(1); } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(ip, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid server name/address %s: %s\n", ip, gai_strerror(rval)); exit(1); } server_keys[server_count].uid = htonl(server_uid); memcpy(&server_keys[server_count].addr, ai_rval->ai_addr, ai_rval->ai_addrlen); server_keys[server_count].has_fingerprint = parse_fingerprint(server_keys[server_count].fingerprint, fingerprint); server_count++; freeaddrinfo(ai_rval); } /** * Set defaults for all command line arguments */ void set_defaults(void) { debug = 0; log_level = DEF_LOG_LEVEL; encrypted_only = 0; uid = 0; dscp = DEF_DSCP; strncpy(logfile, DEF_LOGFILE, sizeof(logfile)-1); logfile[sizeof(logfile)-1] = '\x0'; memset(pidfile, 0, sizeof(pidfile)); interface_count = 0; strncpy(portname, DEF_PORT, sizeof(portname)-1); portname[sizeof(portname)-1] = '\x0'; port = atoi(portname); tempfile = 0; strncpy(tempdir, DEF_TEMPDIR, sizeof(tempdir)-1); tempdir[sizeof(tempdir)-1] = '\x0'; destdircnt = 0; backupcnt = 0; pub_multi_count = 0; key_count = 0; keyfile_count = 0; keyinfo_count = 0; rcvbuf = 0; server_count = 0; has_proxy = 0; sys_keys = 0; memset(hb_hosts, 0, sizeof(hb_hosts)); hbhost_count = 0; hb_interval = DEF_HB_INT; priority = 0; memset(postreceive, 0, sizeof(postreceive)); move_individual = 0; max_log_size = 0; max_log_count = DEF_MAX_LOG_COUNT; } /** * Set argument defaults, read and validate command line options */ void process_args(int argc, char *argv[]) { int c, i, listidx, rval; long tmpval; struct addrinfo ai_hints, *ai_rval; char line[1000], *servername, *ipstr, *fingerprint; char *p, *p2, *hoststr, *portstr, pubname[INET6_ADDRSTRLEN]; FILE *serverfile; const char opts[] = "dx:L:P:s:I:p:tT:D:A:M:B:Q:EU:S:R:k:K:mN:ig:n:h:H:"; set_defaults(); // read lettered arguments while ((c = getopt(argc, argv, opts)) != EOF) { switch (c) { case 'd': debug = 1; break; case 'x': log_level = atoi(optarg); if (log_level < 0) { fprintf(stderr, "Invalid log level\n"); exit(1); } break; case 'L': strncpy(logfile, optarg, sizeof(logfile)-1); logfile[sizeof(logfile)-1] = '\x0'; break; case 'P': strncpy(pidfile, optarg, sizeof(pidfile)-1); pidfile[sizeof(pidfile)-1] = '\x0'; break; case 's': strncpy(postreceive, optarg, sizeof(postreceive)-1); postreceive[sizeof(postreceive)-1] = '\x0'; break; case 'I': p = strtok(optarg, ","); while (p != NULL) { if ((listidx = getifbyname(p, ifl, ifl_len)) != -1) { m_interface[interface_count++] = ifl[listidx]; p = strtok(NULL, ","); continue; } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(p, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid name/address %s: %s\n", p, gai_strerror(rval)); exit(1); } if ((listidx = getifbyaddr((union sockaddr_u *)ai_rval->ai_addr, ifl, ifl_len)) == -1) { fprintf(stderr, "Interface %s not found\n", p); exit(1); } m_interface[interface_count++] = ifl[listidx]; freeaddrinfo(ai_rval); p = strtok(NULL, ","); } break; case 'p': strncpy(portname, optarg, sizeof(portname)-1); portname[sizeof(portname)-1] = '\x0'; port = atoi(portname); if (port == 0) { fprintf(stderr, "Invalid port\n"); exit(1); } break; case 't': tempfile = 1; break; case 'T': strncpy(tempdir, optarg, sizeof(tempdir)-1); tempdir[sizeof(tempdir)-1] = '\x0'; break; case 'D': p = strtok(optarg, ","); while (p != NULL) { strncpy(destdir[destdircnt], p, sizeof(destdir[destdircnt])-1); destdir[destdircnt][sizeof(destdir[destdircnt])-1] = '\x0'; destdircnt++; p = strtok(NULL, ","); } break; case 'A': p = strtok(optarg, ","); while (p != NULL) { strncpy(backupdir[backupcnt],p,sizeof(backupdir[backupcnt])-1); backupdir[backupcnt][sizeof(backupdir[backupcnt])-1] = '\x0'; backupcnt++; p = strtok(NULL, ","); } break; case 'M': p = strtok(optarg, ","); while (p != NULL) { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(p, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid multicast address %s: %s\n", p, gai_strerror(rval)); exit(1); } memcpy(&pub_multi[pub_multi_count], ai_rval->ai_addr, ai_rval->ai_addrlen); pub_multi_count++; freeaddrinfo(ai_rval); p = strtok(NULL, ","); } break; case 'B': rcvbuf = atoi(optarg); if ((rcvbuf < 65536) || (rcvbuf > 104857600)) { fprintf(stderr, "Invalid buffer size\n"); exit(1); } break; case 'Q': tmpval = strtol(optarg, NULL, 0); if ((tmpval < 0) || (tmpval > 63)) { fprintf(stderr, "Invalid dscp\n"); exit(1); } dscp = (tmpval & 0xFF) << 2; break; case 'E': encrypted_only = 1; break; case 'U': errno = 0; uid = strtoul(optarg, NULL, 16); if (errno) { perror("Invalid UID\n"); exit(1); } uid = htonl(uid); break; case 'S': if ((serverfile = fopen(optarg, "r")) == NULL) { fprintf(stderr, "Couldn't open server list %s: %s\n", optarg, strerror(errno)); exit(1); } while (fgets(line, sizeof(line), serverfile)) { while ((strlen(line) != 0) && ((line[strlen(line)-1] == '\r') || (line[strlen(line)-1] == '\n'))) { line[strlen(line)-1] = '\x0'; } if ((line[0] == '#') || (line[0] == '\x0')) { continue; } servername = line; ipstr = strchr(servername, '|'); if (ipstr) { *ipstr = '\x0'; ipstr++; fingerprint = strchr(ipstr, '|'); if (fingerprint) { *fingerprint = '\x0'; fingerprint++; } } else { fingerprint = NULL; } if (strlen(servername) >= DESTNAME_LEN) { fprintf(stderr, "Server list: name too long\n"); exit(1); } add_server_by_name(servername, ipstr, fingerprint); } if (!feof(serverfile) && ferror(serverfile)) { perror("Failed to read from server list file"); exit(1); } fclose(serverfile); break; case 'R': strncpy(line, optarg, sizeof(line)); line[sizeof(line)-1] = '\x0'; servername = strtok(line, "/"); if (!servername) { fprintf(stderr, "Invalid host name\n"); exit(1); } fingerprint = strtok(NULL, "/"); memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(servername, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid proxy address %s: %s\n", servername, gai_strerror(rval)); exit(1); } memcpy(&proxy_info.addr, ai_rval->ai_addr, ai_rval->ai_addrlen); proxy_info.has_fingerprint = parse_fingerprint(proxy_info.fingerprint, fingerprint); has_proxy = 1; freeaddrinfo(ai_rval); break; case 'k': p = strtok(optarg, ","); while (p != NULL) { strncpy(keyfile[keyfile_count], p, sizeof(keyfile[0])-1); keyfile[keyfile_count][sizeof(keyfile[0])-1] = '\x0'; keyfile_count++; p = strtok(NULL, ","); } break; case 'K': p = strtok(optarg, ","); while (p != NULL) { strncpy(keyinfo[keyinfo_count], p, sizeof(keyinfo[0])-1); keyinfo[keyinfo_count][sizeof(keyinfo[0])-1] = '\x0'; keyinfo_count++; p = strtok(NULL, ","); } break; case 'm': sys_keys = 1; break; case 'N': priority = atoi(optarg); if (!valid_priority(priority)) { fprintf(stderr, "Invalid priority value\n"); exit(1); } break; case 'i': move_individual = 1; break; case 'g': max_log_size = atoi(optarg); if ((max_log_size < 1) || (max_log_size > 1024)) { fprintf(stderr, "Invalid max log size\n"); exit(1); } max_log_size *= 1000000; break; case 'n': max_log_count = atoi(optarg); if ((max_log_count < 1) || (max_log_count > 1000)) { fprintf(stderr, "Invalid max log count\n"); exit(1); } break; case 'H': p = strtok(optarg, ","); while (p != NULL) { p2 = strchr(p, ':'); if (p2) { hoststr = strdup(p); hoststr[p2 - p] = '\x0'; portstr = p2 + 1; } else { hoststr = p; portstr = NULL; } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(hoststr, portstr, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid heartbeat address %s: %s\n", p, gai_strerror(rval)); exit(1); } memcpy(&hb_hosts[hbhost_count], ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); if (portstr) { free(hoststr); } else { if (hb_hosts[hbhost_count].ss.ss_family == AF_INET6) { hb_hosts[hbhost_count].sin6.sin6_port = htons(atoi(DEF_PORT)); } else { hb_hosts[hbhost_count].sin.sin_port = htons(atoi(DEF_PORT)); } } hbhost_count++; p = strtok(NULL, ","); } break; case 'h': hb_interval = atoi(optarg); if ((hb_interval <= 0) || (hb_interval > 3600)) { fprintf(stderr, "Invalid hearbeat interval\n"); exit(1); } break; case '?': fprintf(stderr, USAGE); exit(1); } } if (server_count) { for (i = 0; i < pub_multi_count; i++) { if (!is_multicast(&pub_multi[i], 1)) { if ((rval = getnameinfo((struct sockaddr *)&pub_multi[i], family_len(pub_multi[i]), pubname, sizeof(pubname), NULL, 0, NI_NUMERICHOST)) != 0) { fprintf(stderr,"getnameinfo failed: %s",gai_strerror(rval)); } fprintf(stderr, "Invalid source specific " "multicast address: %s\n", pubname); exit(1); } } if (pub_multi_count == 0) { fprintf(stderr, "Default multicast address %s invalid " "for source specific multicast\n", DEF_PUB_MULTI); exit(1); } } if ((keyfile_count != 0) && (keyinfo_count != 0) && (keyfile_count != keyinfo_count)) { fprintf(stderr, "Must list same number of items for -k and -K\n"); exit(1); } if (has_proxy) { if (proxy_info.addr.ss.ss_family == AF_INET6) { proxy_info.addr.sin6.sin6_port = htons(port); } else { proxy_info.addr.sin.sin_port = htons(port); } } if (destdircnt == 0) { strncpy(destdir[0], DEF_DESTDIR, sizeof(destdir[0])-1); destdir[0][sizeof(destdir[0])-1] = '\x0'; destdircnt++; } if ((backupcnt > 0) && (backupcnt != destdircnt)) { fprintf(stderr, "Must specify same number of backup directories " "as destination directories\n"); exit(1); } if (tempfile && (strcmp(tempdir, ""))) { fprintf(stderr, "Cannot specify both -t and -T\n"); exit(1); } } uftp-4.1.5/uftpd.10000644000076400007640000003716312176066071012727 0ustar dbushdbush.TH uftpd 1 "30 July 2013" "UFTP 4.1" .SH NAME uftpd - Encrypted UDP based ftp with multicast - client daemon .SH SYNOPSIS uftpd [ -d ] [ -p port ] [ -B buf_size ] [ -E ] [ -Q dscp ] [ -U UID ] [ -x log_level ] [ -t ] [ -T temp_dir ] [ -D dest_dir[,dest_dir... ]] [ -A backup_dir[,backup_dir... ]] [ -L logfile ] [ -P pidfile ] [ -S serverlist_file ] [ -R proxy[/fp] ] [ -k keyfile[,keyfile...] ] [ -K rsa:key_len | ec:curve[,rsa:key_len | ec:curve...]] [ -m ] [ -N priority ] [ -i ] [ -s postreceive_script ] [ -g max_log_size ] [ -n max_log_count ] [ -H hb_server[:port][,hb_server[:port]...] ] [ -h hb_interval ] [ -I interface[,interface...] ] [ -M pub_mcast_addr[,pub_mcast_addr...] ] .SH DESCRIPTION .P uftpd is the client daemon of the UFTP suite. It listens on one or more multicast addresses to receive files from servers. This version of the client supports servers running UFTP 4.x. .SH OPTIONS .P The following options are supported: .TP .B \-d Enable debug mode. The process will run in the foreground and all output will go to stderr. If specified, the -L option is ignored. .TP .B \-p port The UDP port number to listen on. Default is 1044. .TP .B \-U UID The unique ID for this client. Specified either as an 8 digit hexadecimal number (0xnnnnnnnn). The default value is based on the IP address of the first listed multicast capable interface on the system. If this address is IPv4, the UID is the address. If it is IPv6, the UID is the last 4 bytes of the address. .TP .B \-B buf_size The size in bytes of the UDP receive buffer to use. Valid values are 65536-104857600 (64KB-100MB). Defaults to 262144. .TP .B \-E Only allow incoming sessions if encryption is enabled. Default is to allow both encrypted and unencrypted sessions. .TP .B \-Q dscp Specifies the Differentiated Services Code Point (DSCP), formerly Type of Service (TOS), in the IP header for all outgoing packets. Valid values are 0-63 and may be specified in either decimal or hexadecimal. Default is 0. On Windows XP systems, the OS doesn\(aqt allow this parameter to be changed by default. To change this, add/modify the following DWORD registry value, set to 0, and reboot: HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DisableUserTOSSetting Not currently supported on Windows Vista or later. .TP .B \-x log_level Specifies current logging level. Valid values are 0-5, with 0 being the least verbose and 5 being the most verbose. Default is 2, which is consistent with logging prior to version 3.5. .TP .B \-t Receive each file into a temp file in the same directory as the destination file. The temp file will have an extension of .~uftp-{group-id}-{file-id}, where {group-id} and {file-id} are the group ID of the current session and file ID of the current file. If -A is also specified, the existing destination file is not moved to backup directory until after the file is fully received. .TP .B \-T temp_dir Temp directory in which files are received, then moved to dest_dir when the session is complete. If omitted, files are received directly into dest_dir. Must reside on the same filesystem as the destination directory. The -T option MUST be specified to allow the client to save the state of failed file transfers that can be resumed later. Not compatible -A or -t. Not compatible with -D when multiple destination directories are specified. Also, no incoming files with an absolute path will be accepted if this option is specified. IMPORTANT: When full directories are received, the entire directory is moved at once to the destination directory, removing any existing file/directory. This means that if an existing directory in dest_dir is the same name as a directory received into temp_dir, all files under the existing directory are deleted. The -i option prevents by moving all files individually. .TP .B \-D dest_dir[,dest_dir...] Destination directories for all received files. When an incoming file specifies an absolute path, it must match one of the destination directories, otherwise the file will be rejected. Incoming files that don\(aqt specify an absolute path will be received into the first destination directory in the list. Default is /tmp for UNIX-like systems, C:\\temp for Windows. .TP .B \-A backup_dir[,backup_dir...] Specifies backup directories. Each backup directory corresponds to a destination directory, so the number of each MUST be the same. Existing files that would be overwritten by incoming files are moved to the corresponding backup directory for the selected destination directory, first under timestamped directories, then under the full path of the existing file. For example, if /full/path/to/file would be overwritten, it is moved to {backup_dir}/YYYYMMDD/HHMMSS/full/path/to/file. Under Windows, drive letters for local files are not part of the name, but host/share names for network files are. So C:\\path\\to\\file would be backed up to {backup_dir}\\YYYYMMDD\\HHMMSS\\path\\to\\file, and \\\\host\\share\\path\\to\\file would be backed up to {backup_dir}\\YYYYMMDD\\HHMMSS\\host\\share\\path\\to\\file. Not compatible with -T. .TP .B \-L logfile Specifies the log file. Default is /tmp/uftpd.log for UNIX-like systems systems, C:\\uftpd_log.txt for Windows. .TP .B \-P pidfile The pidfile to write the daemon\(aqs pid to on startup. Default is no pidfile. .TP .B \-S serverlist_file A file containing a list of servers the client will allow to send files to it. The file should contain the ID of the server, the IP address the client expects the server\(aqs request to come from, and optionally the server\(aqs public key fingerprint, with one entry for a server on each line. If a key fingerprint is given, the key specified by the server must match the fingerprint. If your system supports source specific multicast (SSM), the client will subscribe to all public and private multicast addresses using SSM for all servers listed. When this option is specified, the public and private addresses specified by the server must be valid SSM addresses. Any ANNOUNCE that specifies a private IP that is not a valid SSM address will be rejected. Valid SSM addresses are in the 232/8 range for IPv4 and the ff30::/96 range for IPv6. .nf Example contents: 0x11112222|192.168.1.101|66:1E:C9:1D:FC:99:DB:60:B0:1A:F0:8F:CA:F4:28:27:A6:BE:94:BC 0x11113333|fe80::213:72ff:fed6:69ca .fi When expecting to receive from a server that is behind a proxy, the file should list the ID of the server along with the IP and fingerprint of the client proxy. The proxy can authenticate the server. .TP .B \-R proxy[/fingerprint] Specifies the name/IP of the response proxy that all responses are forwarded to. If fingerprint is given, it specifies the proxy\(aqs public key fingerprint. Upon startup, the client will query the proxy for its public key, retrying every 5 seconds until it gets a successful response. The client cannot accept an encrypted file transfer from a server until it gets the proxy\(aqs key. .TP .B \-k keyfile[,keyfile...] .TP .B \-K rsa:key_length | ec:curve[,rsa:key_length | ec:curve...] These two options are used to read and/or write the client\(aqs RSA/ECDSA private keys. The -K option creates one or more RSA or ECDSA private keys. New keys are specified as either rsa:key_length, which creates an RSA private key key_length bits wide, or as ec:curve, which creates an EC key using the curve "curve". The list of supported EC curves is as follows (availability may vary depending on system settings and crypto library used): sect163k1 sect163r1 sect163r2 sect193r1 sect193r2 sect233k1 sect233r1 sect239k1 sect283k1 sect283r1 sect409k1 sect409r1 sect571k1 sect571r1 secp160k1 secp160r1 secp160r2 secp192k1 prime192v1 secp224k1 secp224r1 secp256k1 prime256v1 secp384r1 secp521r1 If only -K is specified, the keys created are not persisted. If only -k is specified, this option reads RSA or ECDSA private keys from each keyfile. If -k and -K are specified, the keys created by -K are written to the keyfiles listed by -k. In this case, -k and -K must give the same number of items. If neither -k nor -K are specified, an RSA private key 512 bytes in length is generated and not persisted. If -k is specified but not -K, the RSA or ECDSA private keys are read from each keyfile. The definition of keyfile is dependent on the crypto library UFTP is compiled to use. On Windows systems, UFTP can built to use either CNG, which is the new API supported by Windows Vista and Windows 7, or CryptoAPI, which is the legacy API and the only one available to Windows XP. Under CryptoAPI, all RSA private keys must be stored in a key container (technically only keys used to sign data, but for UFTP\(aqs purposes this is the case). Key containers are internal to Windows, and each user (and the system) has its own set of key containers. In this case, key_file is actually the name of the key container. When -k is not specified, the generated key is not persisted. Elliptic Curve algorithms are not supported under CryptoAPI. Under CNG, RSA and ECDSA private keys are also stored in key containers, and RSA keys created by CrypoAPI may be read by CNG. Like CryptoAPI, key_file also specifies the key container name, and the generated key is not persisted if -k is not specified. CNG only supports 3 named EC curves: prime256v1, secp384r1, and secp521r1. All other systems use OpenSSL for the crypto library (although under Windows UFTP can be also be built to use it). In this case, key_file specifies a file name where the RSA private key is stored unencrypted in PEM format (the OS is expected to protect this file). When both -k and -K are specified, the file is only written to if it does not currently exist. If the file does exist, an error message will be returned and the server will exit. When -k is not specified, the generated key is not persisted. These PEM files may also be manipulated via the openssl(1) command line tool. Keys can also be generated and viewed via the uftp_keymgt(1) utility. .TP .B \-m For Windows systems using CryptoAPI or CNG, private keys are normally stored in the key container of the running user. Specifying this option stores keys in the system key container. Useful when running as a service. On non-Windows systems, this option has no effect. .TP .B \-N priority Sets the process priority. On Windows systems, valid values are from -2 to 2, with a default of 0. These correspond to the following priorities: .nf -2 High -1 Above Normal 0 Normal 1 Below Normal 2 Low .fi On all other systems, this is the "nice" value. Valid values are from -20 to 19, where -20 is the highest priority and 19 is the lowest priority. Default is 0. .TP .B -i When -T is specified, directories are normally moved from the temp directory to the destionation directory at once, removing all existing files in the that subdirectory within the destionaion directory. This option causes directories to be traversed so that all received files are moved individually, preventing unwanted deletions. This also affects the operation of the -s option. If -T is not specified, this option has no effect. .TP .B -s postreceive_script The full path to an external command or script to be called when files are received. The command will be called as follows: postreceive_script -I session_id file [ file... ] Where "session_id" is an 8 hexadecimal digit number identifiying the current session, and "file" is the full pathname to one or more received files/directories in the destination directory specified by -D. The way this script is called depends on whether or not a temp directory is specified by -T, and if -i is specified. If a temp directory is not specified, or if both -T and -i are specified, the script gets called once for each file as soon as the file is received. If a temp directory is specified but -i is not, the script gets called once at the end of the session, and is passed all top level files/directories received. Here, "top level files/directories" refers to all entries in the temp directory for the session, but not subdirectories. So the script would be responsible for traversing any listed directories to find files contained within them. .TP .B \-g max_log_size Specifies the maximum log file size in MB. Once the log file reaches this size, the file is renamed with a .1 extension and a new log file is opened. For example, if the log file is /tmp/uftpd.log, it will be renamed /tmp/uftpd.log.1 and a new /tmp/uftpd.log will be created. Ignored if -d is specified. Valid values are 1-1024. Default is no log rolling. .TP .B \-n max_log_count Specifies the maximum number of archive log files to keep when log rolling is active. When the log file rolls, archive logs are renamed with an incrementing numerical extension until the max is reached. Archive log files beyond the maximum are deleted. Ignored if -g is not specified. Valid values are 1-1000. Default is 5. .TP .B -H hb_server[:port][,hb_server[:port]...] Lists one or more proxies to send heartbeat messages to. When sending a signed heartbeat message, the first key listed under -k is used to sign the message. If port is not specified for a given proxy, the default port of 1044 is assumed. .TP .B -h hb_interval The time in seconds between sending heartbeat messages. Ignored if -H is not specified. .TP .B \-I interface[,interface...] Lists one or more interfaces to listen to multicast traffic on. Interfaces can be specified either by interface name, by hostname, or by IP. When receiving a closed group membership request, the client will participate if any of these interfaces matches an IP in the announcement. When receiving an open group membership request, the first interface listed is the one the client will report back to the server. This may not necessarily be the interface that the ANNOUNCE was received on. The default is to listen on all active non-loopback interfaces. NOTE: Since Windows doesn\(aqt have named interfaces (not in the sense that UNIX-like systems do), only hostnames or IP addresses are accepted on Windows. If specifying by hostname or IP, may be a mixture of IPv4 and IPv6 addresses, except on systems that don\(aqt support dual mode sockets such as Windows XP. .TP .B \-M pub_multicast_addr[,pub_multicast_addr...] The list of public multicast addresses to listen on. May be a mixture of IPv4 and IPv6 addresses, except on systems that don\(aqt support dual mode sockets such as Windows XP. Default is 230.4.4.1 .SH EXAMPLES .P Starting with the default options: .RS 5 uftpd .RE The client runs as a daemon and listens for announcements on UDP port 1044 on multicast address 230.4.4.1 on all non-loopback network interfaces. Incoming files are received directly into /tmp (C:\\temp on Windows). A 512-bit RSA key is generated to handle encrypted sessions. Suppose you want an external process to handle incoming files in /tmp/dest. Since you don\(aqt want to pick up incomplete files, you might want them to be received into /tmp/receiving then moved to /tmp/dest when done. Then call the client like this: .RS 5 uftpd -D /tmp/dest -T /tmp/receiving .RE If the client expects to receive from different servers, one sending on 230.4.4.1 and one sending on ff02:4:4:2: .RS 5 uftpd -M 230.4.4.1,ff02:4:4:2 .RE To handle incoming encrypted sessions with differing private keys: .RS 5 uftpd -k file_for_rsa_1024_key,file_for_rsa_2048_key,file_for_ec_prime256v1_key .RE If incoming packets aren\(aqt being read quickly enough, and you want to increase the UDP receive buffer size to 2 MB: .RS 5 uftpd -B 2097152 .RE .SH SEE ALSO uftp(1), uftpproxyd(1), uftp_keymgt(1) .SH NOTES The latest version of UFTP can be found at http://uftp-multicast.sourceforge.net. UFTP is covered by the GNU General Public License. Commercial licenses and support are available from Dennis Bush (bush@tcnj.edu). uftp-4.1.5/encrypt_openssl.c0000644000076400007640000010271012250244021015067 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifdef WINDOWS #include #endif #include #include #include #include #include #include #include #include #include "uftp_common.h" #include "encryption.h" /** * Prints OpenSSL errors to log */ static void log_ssl_err(const char *mes) { unsigned long err, found; char errstr[1000]; found = 0; while ((err = ERR_get_error())) { ERR_error_string(err, errstr); log0(0, 0, "%s: %s", mes, errstr); found = 1; } if (!found) { log0(0, 0, "%s", mes); } } static int init_done; /** * Performs all necessary steps to initialize the crypto library */ void crypto_init(int set_sys_key) { // TODO: include calls to RAND_add and the like? OpenSSL_add_all_algorithms(); #ifdef EVP_CIPH_CCM_MODE EVP_add_cipher(EVP_aes_128_ccm()); EVP_add_cipher(EVP_aes_256_ccm()); #endif ERR_load_crypto_strings(); init_done = 1; } /** * Performs all necessary steps to clean up the crypto library */ void crypto_cleanup(void) { if (init_done) { ERR_free_strings(); EVP_cleanup(); } } #ifndef NO_EC /** * Gets the EC curve type associated with a given curve NID */ static uint8_t get_ec_curve_type(int curve) { switch (curve) { case NID_sect163k1: return CURVE_sect163k1; case NID_sect163r1: return CURVE_sect163r1; case NID_sect163r2: return CURVE_sect163r2; case NID_sect193r1: return CURVE_sect193r1; case NID_sect193r2: return CURVE_sect193r2; case NID_sect233k1: return CURVE_sect233k1; case NID_sect233r1: return CURVE_sect233r1; case NID_sect239k1: return CURVE_sect239k1; case NID_sect283k1: return CURVE_sect283k1; case NID_sect283r1: return CURVE_sect283r1; case NID_sect409k1: return CURVE_sect409k1; case NID_sect409r1: return CURVE_sect409r1; case NID_sect571k1: return CURVE_sect571k1; case NID_sect571r1: return CURVE_sect571r1; case NID_secp160k1: return CURVE_secp160k1; case NID_secp160r1: return CURVE_secp160r1; case NID_secp160r2: return CURVE_secp160r2; case NID_secp192k1: return CURVE_secp192k1; case NID_X9_62_prime192v1: return CURVE_secp192r1; case NID_secp224k1: return CURVE_secp224k1; case NID_secp224r1: return CURVE_secp224r1; case NID_secp256k1: return CURVE_secp256k1; case NID_X9_62_prime256v1: return CURVE_secp256r1; case NID_secp384r1: return CURVE_secp384r1; case NID_secp521r1: return CURVE_secp521r1; default: return 0; } } /** * Gets the EC curve NID associated with a given curve */ static int get_ec_curve_nid(uint8_t curve) { switch (curve) { case CURVE_sect163k1: return NID_sect163k1; case CURVE_sect163r1: return NID_sect163r1; case CURVE_sect163r2: return NID_sect163r2; case CURVE_sect193r1: return NID_sect193r1; case CURVE_sect193r2: return NID_sect193r2; case CURVE_sect233k1: return NID_sect233k1; case CURVE_sect233r1: return NID_sect233r1; case CURVE_sect239k1: return NID_sect239k1; case CURVE_sect283k1: return NID_sect283k1; case CURVE_sect283r1: return NID_sect283r1; case CURVE_sect409k1: return NID_sect409k1; case CURVE_sect409r1: return NID_sect409r1; case CURVE_sect571k1: return NID_sect571k1; case CURVE_sect571r1: return NID_sect571r1; case CURVE_secp160k1: return NID_secp160k1; case CURVE_secp160r1: return NID_secp160r1; case CURVE_secp160r2: return NID_secp160r2; case CURVE_secp192k1: return NID_secp192k1; case CURVE_secp192r1: return NID_X9_62_prime192v1; case CURVE_secp224k1: return NID_secp224k1; case CURVE_secp224r1: return NID_secp224r1; case CURVE_secp256k1: return NID_secp256k1; case CURVE_secp256r1: return NID_X9_62_prime256v1; case CURVE_secp384r1: return NID_secp384r1; case CURVE_secp521r1: return NID_secp521r1; default: return 0; } } #endif /** * Gets the EVP_CIPHER associated with a given keytype */ static const EVP_CIPHER *get_cipher(int keytype) { switch (keytype) { case KEY_DES: return EVP_get_cipherbyname("DES-CBC"); case KEY_DES_EDE3: return EVP_get_cipherbyname("DES-EDE3-CBC"); case KEY_AES128_CBC: return EVP_get_cipherbyname("AES-128-CBC"); case KEY_AES256_CBC: return EVP_get_cipherbyname("AES-256-CBC"); case KEY_AES128_GCM: return EVP_get_cipherbyname("id-aes128-GCM"); case KEY_AES256_GCM: return EVP_get_cipherbyname("id-aes256-GCM"); case KEY_AES128_CCM: return EVP_get_cipherbyname("id-aes128-CCM"); case KEY_AES256_CCM: return EVP_get_cipherbyname("id-aes256-CCM"); default: log0(0, 0, "Unknown keytype: %d", keytype); return NULL; } } /** * Gets the EVP_MD associated with a given hashtype */ static const EVP_MD *get_hash(int hashtype) { switch (hashtype) { case HASH_SHA512: return EVP_get_digestbyname("SHA512"); case HASH_SHA384: return EVP_get_digestbyname("SHA384"); case HASH_SHA256: return EVP_get_digestbyname("SHA256"); case HASH_SHA1: return EVP_get_digestbyname("SHA1"); case HASH_MD5: return EVP_get_digestbyname("MD5"); default: log0(0, 0, "Unknown hashtype: %d", hashtype); return NULL; } } /** * Returns whether a particular cipher is supported */ int cipher_supported(int keytype) { return (get_cipher(keytype) != NULL); } /** * Returns whether a particular hash is supported */ int hash_supported(int hashtype) { return (get_hash(hashtype) != NULL); } /** * Gets the key length and IV/block length of a given key */ void get_key_info(int keytype, int *keylen, int *ivlen) { const EVP_CIPHER *cipher = get_cipher(keytype); int mode; if (cipher == NULL) { *keylen = 0; *ivlen = 0; } else { mode = EVP_CIPHER_mode(cipher); *keylen = EVP_CIPHER_key_length(cipher); #ifdef EVP_CIPH_GCM_MODE if (mode == EVP_CIPH_GCM_MODE) { *ivlen = GCM_IV_LEN; } else if (mode == EVP_CIPH_CCM_MODE) { *ivlen = CCM_IV_LEN; } else { *ivlen = EVP_CIPHER_iv_length(cipher); } #else *ivlen = EVP_CIPHER_iv_length(cipher); #endif } } /** * Gets the length of the given hash */ int get_hash_len(int hashtype) { const EVP_MD *hashptr = get_hash(hashtype); if (hashptr == NULL) { return 0; } else { return EVP_MD_size(hashptr); } } /** * Gets num cryptographically random bytes */ int get_random_bytes(unsigned char *buf, int num) { int rval; if (!(rval = RAND_bytes(buf, num))) { log_ssl_err("Error getting random bytes"); } return rval; } /** * Takes a block of data and encrypts it with a symmetric cypher. * For authenticated cipher modes, also takes additional authentication data. * The output buffer must be at least the size of source data + block size. */ int encrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { EVP_CIPHER_CTX ctx; const EVP_CIPHER *cipher = get_cipher(keytype); int mode, len; if (cipher == NULL) { log0(0, 0, "Invalid keytype"); return 0; } mode = EVP_CIPHER_mode(cipher); EVP_CIPHER_CTX_init(&ctx); if (!EVP_EncryptInit_ex(&ctx, cipher, NULL, NULL, NULL)) { log_ssl_err("EncryptInit for cipher failed"); return 0; } #ifdef EVP_CIPH_GCM_MODE if (mode == EVP_CIPH_GCM_MODE) { if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_IVLEN, GCM_IV_LEN, 0)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for IVLEN failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } else if (mode == EVP_CIPH_CCM_MODE) { if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_CCM_SET_IVLEN, CCM_IV_LEN, 0)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for IVLEN failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_CCM_SET_TAG, CCM_TAG_LEN, 0)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for tag len failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } #endif if (!EVP_EncryptInit_ex(&ctx, NULL, NULL, key, IV)) { log_ssl_err("EncryptInit for key/IV failed"); return 0; } len = 0; #ifdef EVP_CIPH_GCM_MODE if ((mode == EVP_CIPH_GCM_MODE) || (mode == EVP_CIPH_CCM_MODE)) { if (mode == EVP_CIPH_CCM_MODE) { if (!EVP_EncryptUpdate(&ctx, NULL, &len, NULL, srclen)) { log_ssl_err("EncryptUpdate for datalen failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } if ((aad != NULL) && (aadlen > 0)) { if (!EVP_EncryptUpdate(&ctx, NULL, &len, aad, aadlen)) { log_ssl_err("EncryptUpdate for authdata failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } } #endif if (!EVP_EncryptUpdate(&ctx, dest, &len, src, srclen)) { log_ssl_err("EncryptUpdate for data failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } *destlen = len; if (!EVP_EncryptFinal_ex(&ctx, dest + *destlen, &len)) { log_ssl_err("EncryptFinal failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } #ifdef EVP_CIPH_GCM_MODE if (mode == EVP_CIPH_GCM_MODE) { if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_LEN, dest + *destlen)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for get tag failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } len += GCM_TAG_LEN; } else if (mode == EVP_CIPH_CCM_MODE) { if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_CCM_GET_TAG, CCM_TAG_LEN, dest + *destlen)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for get tag failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } len += CCM_TAG_LEN; } #endif *destlen += len; EVP_CIPHER_CTX_cleanup(&ctx); return 1; } /** * Takes a block of data encrypted with a symmetric cypher and decrypts it. * For authenticated cipher modes, also takes additional authentication data. * The output buffer must be at least the size of source data. */ int decrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { EVP_CIPHER_CTX ctx; const EVP_CIPHER *cipher = get_cipher(keytype); int mode, len, l_srclen; if (cipher == NULL) { log0(0, 0, "Invalid keytype"); return 0; } mode = EVP_CIPHER_mode(cipher); EVP_CIPHER_CTX_init(&ctx); if (!EVP_DecryptInit_ex(&ctx, cipher, NULL, NULL, NULL)) { log_ssl_err("DecryptInit for cipher failed"); return 0; } #ifdef EVP_CIPH_GCM_MODE if (mode == EVP_CIPH_GCM_MODE) { l_srclen = srclen - GCM_TAG_LEN; if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_IVLEN, GCM_IV_LEN, 0)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for IVLEN failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, GCM_TAG_LEN, (void *)(src + l_srclen))) { log_ssl_err("EVP_CIPHER_CTX_ctrl for set tag failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } else if (mode == EVP_CIPH_CCM_MODE) { l_srclen = srclen - CCM_TAG_LEN; if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_CCM_SET_IVLEN, CCM_IV_LEN, 0)) { log_ssl_err("EVP_CIPHER_CTX_ctrl for IVLEN failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } if (!EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_CCM_SET_TAG, CCM_TAG_LEN, (void *)(src + l_srclen))) { log_ssl_err("EVP_CIPHER_CTX_ctrl for set tag failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } else { l_srclen = srclen; } #else l_srclen = srclen; #endif if (!EVP_DecryptInit_ex(&ctx, NULL, NULL, key, IV)) { log_ssl_err("DecryptInit for key/IV failed"); return 0; } len = 0; #ifdef EVP_CIPH_GCM_MODE if ((mode == EVP_CIPH_GCM_MODE) || (mode == EVP_CIPH_CCM_MODE)) { if (mode == EVP_CIPH_CCM_MODE) { if (!EVP_DecryptUpdate(&ctx, NULL, &len, NULL, l_srclen)) { log_ssl_err("DecryptUpdate for datalen failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } if ((aad != NULL) && (aadlen > 0)) { if (!EVP_DecryptUpdate(&ctx, NULL, &len, aad, aadlen)) { log_ssl_err("DecryptUpdate for authdata failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } } } #endif if (!EVP_DecryptUpdate(&ctx, dest, &len, src, l_srclen)) { log_ssl_err("DecryptUpdate for data failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } *destlen = len; #ifdef EVP_CIPH_CCM_MODE if (mode != EVP_CIPH_CCM_MODE) { #endif if (!EVP_DecryptFinal_ex(&ctx, dest + *destlen, &len)) { log_ssl_err("DecryptFinal failed"); EVP_CIPHER_CTX_cleanup(&ctx); return 0; } *destlen += len; #ifdef EVP_CIPH_CCM_MODE } #endif EVP_CIPHER_CTX_cleanup(&ctx); return 1; } /** * Calculates the HMAC of the given message, hashtype, and hashkey. * dest must be at least the hash length. */ int create_hmac(int hashtype, const unsigned char *key, unsigned int keylen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { const EVP_MD *hashptr = get_hash(hashtype); if (hashptr == NULL) { log0(0, 0, "Invalid hashtype"); return 0; } return (HMAC(hashptr, key, keylen, src, srclen, dest, destlen) != NULL); } /** * Calculates the hash of the given message and hashtype */ int hash(int hashtype, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { EVP_MD_CTX hashctx; const EVP_MD *hashptr = get_hash(hashtype); if (hashptr == NULL) { log0(0, 0, "Invalid hashtype"); return 0; } EVP_MD_CTX_init(&hashctx); if (!EVP_DigestInit_ex(&hashctx, hashptr, NULL)) { log_ssl_err("DigestInit failed"); EVP_MD_CTX_cleanup(&hashctx); return 0; } if (!EVP_DigestUpdate(&hashctx, src, srclen)) { log_ssl_err("DigestUpdate failed"); EVP_MD_CTX_cleanup(&hashctx); return 0; } if (!EVP_DigestFinal_ex(&hashctx, dest, (unsigned int *)destlen)) { log_ssl_err("DigestUpdate failed"); EVP_MD_CTX_cleanup(&hashctx); return 0; } EVP_MD_CTX_cleanup(&hashctx); return 1; } /** * Returns the length in bytes of the modulus for the given RSA key */ int RSA_keylen(const RSA_key_t rsa) { return RSA_size(rsa); } /** * Returns the length of an exported EC public key * An exported key is built as follows: * uint8_t xpoint[ceil(curve_bitlen/8)] * uint8_t ypoint[ceil(curve_bitlen/8)] * uint8_t padding[] */ int EC_keylen(const EC_key_t ec) { #ifndef NO_EC int keylen, padding; if ((keylen = i2o_ECPublicKey(ec, NULL)) == 0) { log0(0, 0, "error getting size of EC key"); return 0; } // Don't count leading "4" keylen--; if ((keylen % 4) == 0) { padding = 0; } else { padding = 4 - (keylen % 4); } return keylen + padding; #else return 0; #endif } /** * Returns the length in bytes of a signature created by the given ECDSA key * ECDSA signatures consist of: * uint16_t rlen * uint16_t slen * uint8_t rsig[ceil(curve_bitlen/8)] * uint8_t ssig[ceil(curve_bitlen/8)] * uint8_t padding[] */ int ECDSA_siglen(const EC_key_t ec) { #ifndef NO_EC return sizeof(uint16_t) + sizeof(uint16_t) + EC_keylen(ec); #else return 0; #endif } /** * Encrypts a small block of data with an RSA public key. * Output buffer must be at least the key size. */ int RSA_encrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen) { int padding; if (RSA_size(rsa) * 8 < 768) { padding = RSA_PKCS1_PADDING; } else { padding = RSA_PKCS1_OAEP_PADDING; } if ((*tolen = RSA_public_encrypt(fromlen, from, to, rsa, padding)) == -1) { log_ssl_err("RSA_public_encrypt failed"); return 0; } return 1; } /** * Decrypts a small block of data with an RSA private key. */ int RSA_decrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen) { int padding; if (RSA_size(rsa) * 8 < 768) { padding = RSA_PKCS1_PADDING; } else { padding = RSA_PKCS1_OAEP_PADDING; } if ((*tolen = RSA_private_decrypt(fromlen, from, to, rsa, padding)) == -1) { log_ssl_err("RSA_private_decrypt failed"); return 0; } return 1; } /** * Hashes a block of data and signs it with an RSA private key. * Output buffer must be at least the key size. */ int create_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen) { unsigned char meshash[HMAC_LEN]; unsigned int meshashlen; const EVP_MD *hashptr; if (!hash(hashtype, mes, meslen, meshash, &meshashlen)) { return 0; } hashptr = get_hash(hashtype); if (hashptr == NULL) { log0(0, 0, "Invalid hashtype"); return 0; } if (!RSA_sign(EVP_MD_type(hashptr), meshash, meshashlen, sig, siglen, rsa)) { log_ssl_err("RSA_sign failed"); return 0; } else { return 1; } } /** * Hashes a block of data and verifies it against an RSA signature. */ int verify_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int siglen) { unsigned char meshash[HMAC_LEN]; unsigned int meshashlen; const EVP_MD *hashptr; if (!hash(hashtype, mes, meslen, meshash, &meshashlen)) { return 0; } hashptr = get_hash(hashtype); if (hashptr == NULL) { log0(0, 0, "Invalid hashtype"); return 0; } if (!RSA_verify(EVP_MD_type(hashptr), meshash, meshashlen, sig, siglen, rsa)) { log_ssl_err("RSA_verify failed"); return 0; } else { return 1; } } /** * Hashes a block of data and signs it with a ECDSA private key. * Output buffer must be at least ECDSA_siglen bytes */ int create_ECDSA_sig(EC_key_t ec, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen) { #ifndef NO_EC unsigned char meshash[HMAC_LEN]; unsigned int meshashlen; const EVP_MD *hashptr; ECDSA_SIG *_sig; uint16_t *rlen, *slen; unsigned char *rval, *sval; if (!hash(hashtype, mes, meslen, meshash, &meshashlen)) { return 0; } hashptr = get_hash(hashtype); if (hashptr == NULL) { log0(0, 0, "Invalid hashtype"); return 0; } if ((_sig = ECDSA_do_sign(meshash, meshashlen, ec)) == NULL) { log_ssl_err("ECDSA_do_sign failed"); return 0; } rlen = (uint16_t *)sig; slen = (uint16_t *)(sig + sizeof(uint16_t)); rval = (unsigned char *)slen + sizeof(uint16_t); sval = rval + BN_num_bytes(_sig->r); *siglen = ECDSA_siglen(ec); memset(sig, 0, *siglen); *rlen = htons(BN_num_bytes(_sig->r)); *slen = htons(BN_num_bytes(_sig->s)); BN_bn2bin(_sig->r, rval); BN_bn2bin(_sig->s, sval); ECDSA_SIG_free(_sig); return 1; #else log0(0, 0, "ECDSA not supported"); return 0; #endif } /** * Hashes a block of data and verifies it against a ECDSA signature. */ int verify_ECDSA_sig(EC_key_t ec, int hashtype, const unsigned char *mes, unsigned int meslen, const unsigned char *sig, unsigned int siglen) { #ifndef NO_EC unsigned char meshash[HMAC_LEN]; unsigned int meshashlen; const EVP_MD *hashptr; ECDSA_SIG *_sig; const uint16_t *rlen, *slen; const unsigned char *rval, *sval; if (!hash(hashtype, mes, meslen, meshash, &meshashlen)) { return 0; } hashptr = get_hash(hashtype); if (hashptr == NULL) { log0(0, 0, "Invalid hashtype"); return 0; } rlen = (const uint16_t *)sig; slen = (const uint16_t *)(sig + sizeof(uint16_t)); rval = (const unsigned char *)slen + sizeof(uint16_t); sval = rval + ntohs(*rlen); if (ntohs(*rlen) + ntohs(*slen) > siglen) { log0(0, 0, "Invalid signature length"); return 0; } _sig = ECDSA_SIG_new(); if (BN_bin2bn(rval, ntohs(*rlen), _sig->r) == NULL) { log_ssl_err("BN_bn2bin failed for r"); ECDSA_SIG_free(_sig); return 0; } if (BN_bin2bn(sval, ntohs(*slen), _sig->s) == NULL) { log_ssl_err("BN_bn2bin failed for r"); ECDSA_SIG_free(_sig); return 0; } if (!ECDSA_do_verify(meshash, meshashlen, _sig, ec)) { log_ssl_err("ECDSA_do_verify failed"); ECDSA_SIG_free(_sig); return 0; } else { ECDSA_SIG_free(_sig); return 1; } #else log0(0, 0, "ECDSA not supported"); return 0; #endif } #ifndef NO_EC /** * Key derivation function for ECDH. * Takes the raw key and returns the SHA-1 hash of the key */ static void *KDF(const void *in, size_t inlen, void *out, size_t *outlen) { if (!hash(HASH_SHA1, in, inlen, out, outlen)) { return NULL; } else { return out; } } #endif /** * Creates an ECDH key based on two EC keys, one public and one private */ int get_ECDH_key(EC_key_t pubkey, EC_key_t privkey, unsigned char *key, unsigned int *keylen) { #ifndef NO_EC if (!ECDH_compute_key(key, 0, EC_KEY_get0_public_key(pubkey), privkey, KDF)) { log_ssl_err("couldn't compute shared key"); return 0; } *keylen = get_hash_len(HASH_SHA1); return 1; #else log0(0, 0, "ECDH not supported"); return 0; #endif } /** * Creates an RSA public key with the given modulus and public exponent */ int import_RSA_key(RSA_key_t *rsa, const unsigned char *keyblob, uint16_t bloblen) { const struct rsa_blob_t *rsablob; const unsigned char *modulus; rsablob = (const struct rsa_blob_t *)keyblob; modulus = keyblob + sizeof(struct rsa_blob_t); if (sizeof(struct rsa_blob_t) + ntohs(rsablob->modlen) != bloblen) { log0(0, 0, "Error importing RSA key: invalid length"); return 0; } *rsa = RSA_new(); if (((*rsa)->e = BN_bin2bn((const unsigned char *)&rsablob->exponent, 4, NULL)) == NULL) { log_ssl_err("BN_bin2bn failed for e"); return 0; } if (((*rsa)->n = BN_bin2bn(modulus, ntohs(rsablob->modlen),NULL)) == NULL) { log_ssl_err("BN_bin2bn failed for n"); return 0; } return 1; } /** * Extracts the modulus and public exponent from an RSA public key */ int export_RSA_key(const RSA_key_t rsa, unsigned char *keyblob, uint16_t *bloblen) { struct rsa_blob_t *rsablob; unsigned char *modulus; unsigned char bin_exponent[4]; uint32_t exponent; int explen, modlen, i; rsablob = (struct rsa_blob_t *)keyblob; modulus = keyblob + sizeof(struct rsa_blob_t); if (BN_num_bytes(rsa->e) > sizeof(bin_exponent)) { log0(0, 0, "exponent too big for export"); return 0; } if ((explen = BN_bn2bin(rsa->e, bin_exponent)) <= 0) { log_ssl_err("BN_bn2bin failed for e"); return 0; } if (explen > 4) { log0(0, 0, "exponent too big, size %d", explen); return 0; } exponent = 0; for (i = 0; i < explen; i++) { exponent |= bin_exponent[i] << (8 * (explen - i - 1)); } if ((modlen = BN_bn2bin(rsa->n, modulus)) <= 0) { log_ssl_err("BN_bn2bin failed for n"); return 0; } rsablob->blobtype = KEYBLOB_RSA; rsablob->reserved = 0; rsablob->modlen = htons(modlen); rsablob->exponent = htonl(exponent); *bloblen = sizeof(struct rsa_blob_t) + modlen; return 1; } /** * Creates an EC public key with the curve and key value */ int import_EC_key(EC_key_t *ec, const unsigned char *keyblob, uint16_t bloblen, int isdh) { #ifndef NO_EC const struct ec_blob_t *ecblob; const unsigned char *keyval, *tmp; unsigned char *buf; ecblob = (const struct ec_blob_t *)keyblob; keyval = keyblob + sizeof(struct ec_blob_t); if (sizeof(struct ec_blob_t) + ntohs(ecblob->keylen) > bloblen) { log0(0, 0, "Error importing EC key: invalid length"); return 0; } if ((*ec = EC_KEY_new_by_curve_name( get_ec_curve_nid(ecblob->curve))) == NULL) { log_ssl_err("EC_KEY_new_by_curve_name failed"); return 0; } buf = safe_malloc(ntohs(ecblob->keylen) + 1); buf[0] = 4; memcpy(&buf[1], keyval, ntohs(ecblob->keylen)); tmp = buf; if (!o2i_ECPublicKey(ec, &tmp, ntohs(ecblob->keylen) + 1)) { log_ssl_err("o2i_ECPublicKey failed"); EC_KEY_free(*ec); free(buf); return 0; } free(buf); return 1; #else log0(0, 0, "EC keys not supported"); return 0; #endif } /** * Extracts the key value from an EC public key */ int export_EC_key(const EC_key_t ec, unsigned char *keyblob, uint16_t *bloblen) { #ifndef NO_EC struct ec_blob_t *ecblob; unsigned char *keyval, *buf, *tmp; int keylen; ecblob = (struct ec_blob_t *)keyblob; keyval = keyblob + sizeof(struct ec_blob_t); if ((keylen = i2o_ECPublicKey(ec, NULL)) == 0) { log0(0, 0, "error getting size of EC key"); return 0; } buf = safe_malloc(keylen); tmp = buf; // After this call, tmp points to (buf + keylen), // but the exported key lives at buf if (!i2o_ECPublicKey(ec, &tmp)) { log_ssl_err("i2o_ECPublicKey failed"); free(buf); return 0; } // Keyblob may contain trailing padding; ensure it's zero'ed out *bloblen = sizeof(struct ec_blob_t) + EC_keylen(ec); memset(keyblob, 0, *bloblen); ecblob->blobtype = KEYBLOB_EC; ecblob->curve = get_EC_curve(ec); // Don't copy the leading "4" keylen--; memcpy(keyval, &buf[1], keylen); ecblob->keylen = htons(keylen); free(buf); return 1; #else log0(0, 0, "EC keys not supported"); return 0; #endif } /** * Generates an RSA private key with the given exponent and number of bits * and writes it to the given file (if specified). */ RSA_key_t gen_RSA_key(int bits, int exponent, const char *filename) { RSA_key_t rsa; FILE *f; if ((rsa = RSA_generate_key(bits ? bits : DEF_RSA_LEN, exponent, NULL, NULL)) == NULL) { log_ssl_err("couldn't generate rsa key"); return NULL; } if (filename && strcmp(filename, "")) { if ((f = fopen(filename, "rb")) != NULL) { log0(0, 0, "Private key file already exists, won't overwrite"); fclose(f); return NULL; } if ((f = fopen(filename, "wb")) == NULL) { syserror(0, 0, "failed to open key file"); return NULL; } if (!PEM_write_RSAPrivateKey(f, rsa, NULL, NULL, 0, NULL, NULL)) { log_ssl_err("couldn't write rsa private key"); fclose(f); return NULL; } fclose(f); } return rsa; } /** * Reads an RSA private key from the specified file */ RSA_key_t read_RSA_key(const char *filename) { RSA_key_t rsa; FILE *f; if ((f = fopen(filename, "rb")) == NULL) { syserror(0, 0, "failed to open key file"); return NULL; } if ((rsa = PEM_read_RSAPrivateKey(f, NULL, NULL, NULL)) == NULL) { log_ssl_err("couldn't read rsa private key"); return NULL; } fclose(f); return rsa; } /** * Generates an EC private key with the given curve * and writes it to the given file (if specified). */ EC_key_t gen_EC_key(uint8_t curve, int isdh, const char *filename) { #ifndef NO_EC EC_key_t ec; FILE *f; if (curve == 0) { curve = DEF_CURVE; } if ((ec = EC_KEY_new_by_curve_name(get_ec_curve_nid(curve))) == NULL) { log_ssl_err("EC_KEY_new_by_curve_name failed"); return NULL; } if (!EC_KEY_generate_key(ec)) { log_ssl_err("EC_KEY_generate_key failed"); return NULL; } // Needed to write the PEM with a named curve EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); if (filename && strcmp(filename, "")) { if ((f = fopen(filename, "rb")) != NULL) { log0(0, 0, "Private key file already exists, won't overwrite"); fclose(f); return NULL; } if ((f = fopen(filename, "wb")) == NULL) { syserror(0, 0, "failed to open key file"); return NULL; } if (!PEM_write_ECPrivateKey(f, ec, NULL, NULL, 0, NULL, NULL)) { log_ssl_err("couldn't write EC private key"); fclose(f); return NULL; } fclose(f); } return ec; #else log0(0, 0, "EC keys not supported"); return NULL; #endif } /** * Reads an EC private key from the specified file */ EC_key_t read_EC_key(const char *filename) { #ifndef NO_EC EC_key_t ec; FILE *f; if ((f = fopen(filename, "rb")) == NULL) { syserror(0, 0, "failed to open key file"); return NULL; } if ((ec = PEM_read_ECPrivateKey(f, NULL, NULL, NULL)) == NULL) { log_ssl_err("couldn't read EC private key"); return NULL; } fclose(f); return ec; #else log0(0, 0, "EC keys not supported"); return NULL; #endif } /** * Reads a private key of uknown type */ union key_t read_private_key(const char *filename, int *keytype) { union key_t key; FILE *f; key.key = 0; if ((f = fopen(filename, "rb")) == NULL) { syserror(0, 0, "failed to open key file"); *keytype = 0; return key; } if ((key.rsa = PEM_read_RSAPrivateKey(f, NULL, NULL, NULL)) != NULL) { fclose(f); *keytype = KEYBLOB_RSA; return key; } fclose(f); #ifndef NO_EC key.key = 0; if ((f = fopen(filename, "rb")) == NULL) { syserror(0, 0, "failed to open key file"); *keytype = 0; return key; } if ((key.ec = PEM_read_ECPrivateKey(f, NULL, NULL, NULL)) != NULL) { *keytype = KEYBLOB_EC; } else { log0(0, 0, "Failed to read key"); *keytype = 0; } fclose(f); return key; #else log0(0, 0, "Failed to read key"); key.key = 0; *keytype = 0; return key; #endif } /** * Returns the EC curve type of the specified EC key */ uint8_t get_EC_curve(const EC_key_t ec) { #ifndef NO_EC int nid; nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); return get_ec_curve_type(nid); #else log0(0, 0, "EC keys not supported"); return 0; #endif } void free_RSA_key(RSA_key_t rsa) { RSA_free(rsa); } void free_EC_key(EC_key_t ec) { #ifndef NO_EC EC_KEY_free(ec); #endif } const char *get_next_container() { return NULL; } void delete_container(const char *name) { } void set_sys_keys(int set) { } uftp-4.1.5/server_send.h0000644000076400007640000000265112250244021014167 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_SEND_H #define _SERVER_SEND_H int send_files(void); #endif // _SERVER_SEND_H uftp-4.1.5/client_transfer.c0000644000076400007640000011255512250244021015032 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #include #endif #include "client.h" #include "client_common.h" #include "client_transfer.h" void move_files_individual(struct group_list_t *group, const char *local_temp, char *local_dest); /** * 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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "Max pathname length exceeded: %s%c%s", local_dest, PATH_SEP, filename); return; } if (lstat_func(temppath, &temp_stat) == -1) { syserror(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "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); log0(group->group_id, group->file_id, "error (%d): %s", GetLastError(), errbuf); } #else if (rename(temppath, destpath) == -1) { syserror(group->group_id, group->file_id, "Couldn't move file"); } #endif run_postreceive(group, destpath); } } /** * Move all files from temp to destination directory if at end of group. * Called recursively to move each file individually. */ void move_files_individual(struct group_list_t *group, const char *local_temp, char *local_dest) { int emptydir; { #ifdef WINDOWS intptr_t ffhandle; struct _finddatai64_t finfo; char dirglob[MAXPATHNAME]; snprintf(dirglob, sizeof(dirglob), "%s%c*", local_temp, PATH_SEP); if ((ffhandle = _findfirsti64(dirglob, &finfo)) == -1) { syserror(group->group_id, group->file_id, "Failed to open directory %s", dirglob); return; } emptydir = 1; do { if (strcmp(finfo.name, ".") && strcmp(finfo.name, "..")) { emptydir = 0; move_file_individual(group, local_temp, local_dest, finfo.name); } } while (_findnexti64(ffhandle, &finfo) == 0); _findclose(ffhandle); #else DIR *dir; struct dirent *de; if ((dir = opendir(local_temp)) == NULL) { syserror(group->group_id, group->file_id, "Failed to open directory %s", local_temp); return; } emptydir = 1; // 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) { if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { emptydir = 0; move_file_individual(group, local_temp, local_dest, de->d_name); } } if (errno && (errno != ENOENT)) { syserror(group->group_id, group->file_id, "Failed to read directory %s", tempdir); } closedir(dir); #endif } if (emptydir) { run_postreceive(group, local_dest); } if (rmdir(local_temp) == -1) { syserror(group->group_id, group->file_id, "Failed remove temp directory %s", local_temp); } } /** * 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)) { log0(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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); log0(group->group_id, group->file_id, "error (%d): %s", GetLastError(), errbuf); } filelist[filecount] = strdup(destpath); if (filelist[filecount] == NULL) { syserror(0, 0, "strdup failed!"); exit(1); } 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) { syserror(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "Couldn't move file"); } filelist[filecount] = strdup(destpath); if (filelist[filecount] == NULL) { syserror(0, 0, "strdup failed!"); exit(1); } filecount++; } } if (errno && (errno != ENOENT)) { syserror(group->group_id, group->file_id, "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) { syserror(group->group_id, group->file_id, "Failed remove temp directory %s", temppath); } } /** * Sets the fields in a EXT_TFMCC_ACK_INFO extension for transmission */ void set_tfmcc_ack_info(struct group_list_t *group, struct tfmcc_ack_info_he *tfmcc) { struct timeval now, send_time; unsigned ccrate; tfmcc->exttype = EXT_TFMCC_ACK_INFO; tfmcc->extlen = sizeof(struct tfmcc_ack_info_he) / 4; tfmcc->cc_seq = htons(group->ccseq); ccrate = current_cc_rate(group); log4(group->group_id, 0, "ccrate=%d", ccrate); tfmcc->cc_rate = htons(quantize_rate(ccrate)); //tfmcc->cc_rate = htons(quantize_rate(current_cc_rate(group))); tfmcc->flags = 0; if (group->slowstart) { tfmcc->flags |= FLAG_CC_START; } if (group->rtt != 0.0) { tfmcc->flags |= FLAG_CC_RTT; } tfmcc->client_id = uid; 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)); } tfmcc->tstamp_sec = htonl((uint32_t)send_time.tv_sec); tfmcc->tstamp_usec = htonl((uint32_t)send_time.tv_usec); } /** * 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)) { log0(group->group_id, group->file_id, "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) { sockerror(group->group_id, group->file_id, "Error sending STATUS"); } else { log2(group->group_id, group->file_id, "Sent %d NAKs for section %d", nak_count, section); } free(buf); free(encrypted); } /** * Sets the fields in a EXT_FREESPACE_INFO extension for transmission */ void set_freespace_info(struct group_list_t *group, struct freespace_info_he *freespace) { int64_t disk_space; disk_space = free_space(group->fileinfo.filepath); freespace->exttype = EXT_FREESPACE_INFO; freespace->extlen = sizeof(struct freespace_info_he) / 4; freespace->freespace_hi = htonl((uint32_t)(disk_space >> 32)); freespace->freespace_lo = htonl((uint32_t)(disk_space & 0xFFFFFFFF)); } /** * 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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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) { sockerror(group->group_id, group->file_id, "Error sending COMPLETE"); } else { log2(group->group_id, group->file_id, "COMPLETE sent"); } set_timeout(group, 0); free(buf); free(encrypted); } /** * 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)) { log0(group->group_id, group->file_id, "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) { sockerror(group->group_id, group->file_id, "Error sending CC_ACK"); } else { log2(group->group_id, group->file_id, "CC_ACK sent"); } set_timeout(group, 0); group->cc_time.tv_sec = 0; group->cc_time.tv_usec = 0; free(buf); } /** * Starts a new feedback round under TFMCC */ void init_tfmcc_fb_round(struct group_list_t *group, uint16_t new_ccseq) { double urand, backoff; group->ccseq = new_ccseq; urand = (double)(rand32() + 0.0) / 0xFFFFFFFF; if (urand == 0.0) urand = 1.0; backoff = 6 * group->grtt * (1 + log(urand) / log(group->gsize)); if (backoff < 0) backoff = 0.0; gettimeofday(&group->cc_time, NULL); add_timeval_d(&group->cc_time, (backoff > 0) ? backoff : 0); group->initrate = current_cc_rate(group); log3(group->group_id, group->file_id, "Starting feedback round %d: " "backoff = %.3f, initrate = %d", new_ccseq, backoff, group->initrate); } /** * Reads a EXT_TFMCC_DATA_INFO extension in a FILESEG message */ void handle_tfmcc_data_info(struct group_list_t *group, const struct tfmcc_data_info_he *tfmcc) { uint32_t ccrate; uint16_t new_ccseq; new_ccseq = ntohs(tfmcc->cc_seq); ccrate = unquantize_rate(ntohs(tfmcc->cc_rate)); if (((int16_t)(new_ccseq - group->ccseq)) > 0) { init_tfmcc_fb_round(group, new_ccseq); } else if ((group->cc_time.tv_sec != 0) && ((ccrate < current_cc_rate(group)) || (ccrate < group->initrate)) && (group->grtt > group->rtt)) { log4(group->group_id, group->file_id, "Canceling feedback timer"); group->cc_time.tv_sec = 0; group->cc_time.tv_usec = 0; } } /** * 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, seq, wrote_len; unsigned extlen; f_offset_t offset, seek_rval; if (group->fileinfo.ftype != FTYPE_REG) { log2(group->group_id, group->file_id, "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))) { log2(group->group_id, group->file_id, "Rejecting FILESEG: invalid message size"); return; } if (ntohs(fileseg->file_id) != group->file_id) { log2(group->group_id, group->file_id, "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)) { log2(group->group_id, group->file_id, "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)) { log2(group->group_id, group->file_id, "Rejecting FILESEG: invalid data size %d", datalen); return; } if (log_level >= 5) { log5(group->group_id, group->file_id, "Got packet %d", seq); } else if (log_level == 4) { if (seq != group->fileinfo.last_block + 1) { log4(group->group_id, group->file_id, "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.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; log3(group->group_id, group->file_id, "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]) { offset = (f_offset_t) seq * group->blocksize; if ((seek_rval = lseek_func(group->fileinfo.fd, offset - group->fileinfo.curr_offset, SEEK_CUR)) == -1) { syserror(group->group_id, group->file_id, "lseek failed for file"); } if (seek_rval != offset) { log2(group->group_id, group->file_id, "offset is %s", printll(seek_rval)); log2(group->group_id, group->file_id, " should be %s", printll(offset)); if ((seek_rval = lseek_func(group->fileinfo.fd, seek_rval - group->fileinfo.curr_offset, SEEK_CUR)) == -1) { syserror(group->group_id, group->file_id, "lseek failed for file"); return; } } if ((wrote_len = write(group->fileinfo.fd, data, datalen)) == -1) { syserror(group->group_id, group->file_id, "Write failed for segment %d", seq); } else { group->fileinfo.curr_offset = offset + wrote_len; if (wrote_len != datalen) { log0(group->group_id, group->file_id, "Write failed for segment %d, only wrote %d bytes", seq, wrote_len); } else { group->fileinfo.naklist[seq] = 0; } } } set_timeout(group, 0); } /** * Returns 1 if a file has been completely received, 0 otherwise */ int file_done(struct group_list_t *group, int detail) { unsigned int section_offset, blocks_this_sec, section, block, nakidx; if ((group->phase == PHASE_MIDGROUP) || (group->file_id == 0)) { return 1; } for (section = 0; section < group->fileinfo.sections; section++) { if (!group->fileinfo.section_done[section]) { if (!detail) { return 0; } if (section >= group->fileinfo.big_sections) { section_offset = (group->fileinfo.big_sections * group->fileinfo.secsize_big) + ((section - group->fileinfo.big_sections) * group->fileinfo.secsize_small); blocks_this_sec = group->fileinfo.secsize_small; } else { section_offset = section * group->fileinfo.secsize_big; blocks_this_sec = group->fileinfo.secsize_big; } for (block = 0; block < blocks_this_sec; block++) { nakidx = block + section_offset; if (group->fileinfo.naklist[nakidx]) { return 0; } } group->fileinfo.section_done[section] = 1; } } return 1; } /** * Build the NAK list for a given section. Returns the NAK count. */ unsigned int get_naks(struct group_list_t *group, unsigned int section, unsigned char **naks) { unsigned int section_offset, blocks_this_sec, i; unsigned int nakidx, naklistidx, numnaks; if ((group->phase == PHASE_MIDGROUP) || (group->file_id == 0) || (group->fileinfo.section_done[section])) { *naks = NULL; return 0; } if (section >= group->fileinfo.big_sections) { section_offset = (group->fileinfo.big_sections * group->fileinfo.secsize_big) + ((section - group->fileinfo.big_sections) * group->fileinfo.secsize_small); blocks_this_sec = group->fileinfo.secsize_small; } else { section_offset = section * group->fileinfo.secsize_big; blocks_this_sec = group->fileinfo.secsize_big; } log3(group->group_id, group->file_id, "getting naks: section: %d, offset: %d, blocks: %d", section, section_offset, blocks_this_sec); *naks = safe_calloc(group->blocksize, 1); // Build NAK list numnaks = 0; for (i = 0; i < blocks_this_sec; i++) { nakidx = i + section_offset; naklistidx = i; if (group->fileinfo.naklist[nakidx]) { log4(group->group_id, group->file_id, "NAK for %d", nakidx); // Each bit represents a NAK; set each one we have a NAK for // Simplified: *naks[naklistidx / 8] |= (1 << (naklistidx % 8)) (*naks)[naklistidx >> 3] |= (1 << (naklistidx & 7)); numnaks++; } } // Highly verbose debugging -- print NAK list to send if (log_level >= 5) { for (i = 0; i < group->blocksize; i++) { sclog5("%02X ", (*naks)[i]); if (i % 25 == 24) slog5(""); } slog5(""); } if (numnaks == 0) { group->fileinfo.section_done[section] = 1; } return numnaks; } /** * Processes an expected DONE message */ void handle_done(struct group_list_t *group, const unsigned char *message, unsigned meslen) { const struct done_h *done; const uint32_t *addrlist; unsigned int section, listlen; done = (const struct done_h *)message; addrlist = (const uint32_t *)(message + (done->hlen * 4)); listlen = (meslen - (done->hlen * 4)) / 4; if ((meslen < (done->hlen * 4U)) || ((done->hlen * 4U) < sizeof(struct done_h))) { log2(group->group_id, group->file_id, "Rejecting DONE: invalid message size"); return; } section = ntohs(done->section); if ((ntohs(done->file_id) != group->file_id) && (ntohs(done->file_id) != 0) && (group->phase != PHASE_MIDGROUP)) { // Silently reject if not for this file and not end of group return; } if (ntohs(done->file_id) == 0) { // We're at end of group, so set local file_id=0 to flag this group->file_id = 0; } if (group->file_id) { log1(group->group_id, group->file_id, "Got DONE message for section %d", section); } else { log1(group->group_id, group->file_id, "Got DONE message for group"); } if (uid_in_list(addrlist, listlen)) { if (group->file_id == 0) { log1(group->group_id, 0, "Group complete"); group->phase = PHASE_COMPLETE; group->fileinfo.comp_status = COMP_STAT_NORMAL; gettimeofday(&group->expire_time, NULL); if (group->robust * group->grtt < 1.0) { add_timeval_d(&group->expire_time, 1.0); } else { add_timeval_d(&group->expire_time, group->robust * group->grtt); } send_complete(group, 0); } else { if (file_done(group, 1)) { log1(group->group_id, group->file_id, "File transfer complete"); group->fileinfo.nak_time.tv_sec = 0; group->fileinfo.nak_time.tv_usec = 0; send_complete(group, 0); file_cleanup(group, 0); } else if (group->fileinfo.nak_time.tv_sec == 0) { log4(group->group_id, group->file_id, "Setting nak_time to trigger in %.6f", group->grtt); 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 + 1; group->fileinfo.got_done = 1; log3(group->group_id, group->file_id, "Got DONE for client, " "set NAK timer for sections %d - %d", group->fileinfo.nak_section_first, group->fileinfo.nak_section_last); } group->fileinfo.last_section = section + 1; } } else if (group->phase != PHASE_MIDGROUP) { if ((section + 1 > 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 + 1; group->fileinfo.got_done = 0; log3(group->group_id, group->file_id, "Got DONE, set NAK timer " "for sections %d - %d", group->fileinfo.nak_section_first, group->fileinfo.nak_section_last); } group->fileinfo.last_section = section + 1; } set_timeout(group, 0); } /** * Processes an expected DONE_CONF message */ void handle_done_conf(struct group_list_t *group, const unsigned char *message, unsigned meslen) { const struct doneconf_h *doneconf; const uint32_t *addrlist; int listlen; doneconf = (const struct doneconf_h *)message; addrlist = (const uint32_t *)(message + sizeof(struct doneconf_h)); listlen = (meslen - (doneconf->hlen * 4)) / 4; if ((meslen < (doneconf->hlen * 4U)) || ((doneconf->hlen * 4U) < sizeof(struct doneconf_h))) { log2(group->group_id, group->file_id, "Rejecting DONE_CONF: invalid message size"); return; } if (uid_in_list(addrlist, listlen)) { log0(group->group_id, group->file_id, "Group file transfer confirmed"); move_files(group); file_cleanup(group, 0); } } /** * Processes an expected CONG_CTRL message */ void handle_cong_ctrl(struct group_list_t *group, const unsigned char *message, unsigned meslen, struct timeval rxtime) { const struct cong_ctrl_h *cong_ctrl; const struct cc_item *cc_list; int listlen, i, ccidx, clridx; uint32_t ccrate; uint16_t new_ccseq; double new_rtt; cong_ctrl = (const struct cong_ctrl_h *)message; cc_list = (const struct cc_item *)(message + sizeof(struct cong_ctrl_h)); listlen = (meslen - (cong_ctrl->hlen * 4)) / sizeof(struct cc_item); if ((meslen < (cong_ctrl->hlen * 4U)) || ((cong_ctrl->hlen * 4U) < sizeof(struct cong_ctrl_h))) { log2(group->group_id, group->file_id, "Rejecting CONG_CTRL: invalid message size"); return; } if (group->cc_type != CC_TFMCC) { log3(group->group_id, group->file_id, "Rejecting CONG_CTRL: " "not allowed for given congestion control type"); return; } new_ccseq = ntohs(cong_ctrl->cc_seq); ccrate = unquantize_rate(ntohs(cong_ctrl->cc_rate)); group->last_server_ts.tv_sec = ntohl(cong_ctrl->tstamp_sec); group->last_server_ts.tv_usec = ntohl(cong_ctrl->tstamp_usec); group->last_server_rx_ts = rxtime; for (clridx = -1, ccidx = -1, i = 0; (i < listlen) && ((clridx == -1) || (ccidx == -1)); i++) { if (cc_list[i].dest_id == uid) { ccidx = i; } if ((cc_list[i].flags & FLAG_CC_CLR) != 0) { clridx = i; } } if (ccidx != -1) { new_rtt = unquantize_grtt(cc_list[ccidx].rtt); if (ccidx == clridx) { group->isclr = 1; send_cc_ack(group); } else { group->isclr = 0; } if (group->isclr) { group->rtt = (0.9 * group->rtt) + (0.1 * new_rtt); } else { group->rtt = (0.5 * group->rtt) + (0.5 * new_rtt); } } else { group->isclr = 0; } if (((int16_t)(new_ccseq - group->ccseq)) > 0) { init_tfmcc_fb_round(group, new_ccseq); } else if ((group->cc_time.tv_sec != 0) && ((ccrate < current_cc_rate(group)) || (ccrate < group->initrate)) && (group->grtt > group->rtt)) { log4(group->group_id, group->file_id, "Canceling feedback timer"); group->cc_time.tv_sec = 0; group->cc_time.tv_usec = 0; } } uftp-4.1.5/server_phase.h0000644000076400007640000000305712137013343014344 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2012 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_PHASE_H #define _SERVER_PHASE_H #include "server.h" int announce_phase(struct finfo_t *finfo); int transfer_phase(struct finfo_t *finfo); int completion_phase(struct finfo_t *finfo); #endif // _SERVER_PHASE_H uftp-4.1.5/uftp.10000644000076400007640000006075012176075022012555 0ustar dbushdbush.TH uftp 1 "30 July 2013" "UFTP 4.1" .SH NAME uftp - Encrypted UDP based ftp with multicast - server .SH SYNOPSIS uftp [ -R txrate ] [ -L logfile ] [ -B udp_buf_size ] [ -g max_log_size ] [ -n max_log_count ] [ -Y keytype ] [ -h hashtype ] [ -w sigtype ] [ -e keyextype[:curve] ] [ -c ] [ -m max_nak_count ] [ -k key_file ] [ -K key_length | curve ] [ -l ] [ -T ] [ -b block_size ] [ -t ttl ] [ -Q dscp ] [ -z | -Z ] [ -I interface ] [ -p port ] [ -u source_port ] [ -j proxylist_file ] [ -q ] [ -f ] [ -y ] [ -x log_level ] [ -W txweight ] [ -H host[,host...] | -H @hostlist_file | -F restart_file ] [ -X exclude_file ] [ -M pub_multicast_addr ] [ -P priv_multicast_addr ] [ -N max_nak_pct ] [ -C cc_type ] [ -o ][ -D dest_name ] [ -E base_dir[,base_dir... ] ] [ -S status_file ] [ -r init_grtt[:min_grtt:max_grtt] ] [ -s robust ] { -i list_file | file [ file... ] } .SH DESCRIPTION .P uftp is the server process of the UFTP suite. It sends one or more files to one or more receivers via multicast with optional encryption. .SH OPTIONS .P The following options are supported: .TP .B \-R txrate The transmission speed in Kbps. Specifying -1 for this value results in data being sent as fast as the network interface will allow. Using a value of -1 is recommended only if the network path between the server and all clients is as fast as the server\(aqs local interface, and works best in a gigabit environment. Default is 1000 Kbps. Ignored if -C is given any value other than "none". .TP .B \-L logfile Specifies the log file. Default is to write to stderr. .TP .B \-B buf_size The size in bytes of the UDP send buffer and receive buffer to use. Valid values are 65536-104857600 (64KB-100MB). Defaults to 262144. .TP .B \-g max_log_size Specifies the maximum log file size in MB. Once the log file reaches this size, the file is renamed with a .1 extension and a new log file is opened. For example, if the log file is /tmp/uftp.log, it will be renamed /tmp/uftp.log.1 and a new /tmp/uftp.log will be created. Ignored if -L is not specified. Valid values are 1-1024. Default is no log rolling. .TP .B \-n max_log_count Specifies the maximum number of archive log files to keep when log rolling is active. When the log file rolls, archive logs are renamed with an incrementing numerical extension until the max is reached. Archive log files beyond the maximum are deleted. Ignored if -L and -g are not specified. Valid values are 1-1000. Default is 5. .TP .B \-Y keytype The symmetric encryption algorithm to use. Valid values are "des" for DES in CBC mode, "3des" for three key Triple DES in CBC mode, "aes128-cbc", "aes256-cbc", "aes128-gcm", "aes256-gcm", "aes128-ccm", "aes256-ccm", or "none" to not set up encryption at all. The GCM and CCM modes are authenticated encryption modes which applies a signatures at the same time as encryption. If one of these modes are specifies, the value of -w is ignored. Default is "none". Not all installations may support all of these algorithms. .TP .B \-h hashtype The hashing algorithm to use for key derivation and HMAC signatures. Valid values are "sha1" for SHA-1, "sha256" for SHA-256, "sha384" for SHA-384, and "sha512" for SHA-512. Defaults to "sha1". Ignored if -Y is "none". Not all installations may support all of these algorithms. .TP .B \-w sigtype Specifies the type of signature to be applied to encrypted messages. Valid values are "hmac" to apply an HMAC to the encrypted message, and "keyex" to apply either an RSA or ECDSA signature depending on the key exchange algorithm chosen via -e. HMAC signatures are based off the group master key and ensure the sender of a message is a valid member of the group, but does not guarantee that the message came from a specific group member. RSA and ECDSA signatures ensure that messages come from a particular member, but is much much slower to calculate than HMAC and creates a larger per-packet overhead. If the keytype specified by -Y is an authentication mode cipher (i.e. AES in GCM or CCM mode), this field is ignored and signatures will instead be generated at the same time data is encrypted. This also has the lowest size overhead and is the fastest. Default is "hmac". Ignored if -Y is "none". .TP .B \-e keyextype[:curve] Specifies the key exchange algorithm to use. Valid values are "rsa" for an RSA key exchange, "ecdh_rsa" for an Elliptic Curve Diffie-Hellman (ECDH) key exchange with RSA signatures, and "ecdh_ecdsa" for an ECDH key exchange with ECDSA signatures. Using one of the ECDH schemes provides perfect forward security, while using just RSA is slightly more resilient to replay attacks. If ecdh_rsa or ecdh_ecdsa are chosen, the named EC curve that the ECDH key is based on may optionally be selected, with prime256v1 as the default (See -k and -K for the list of available EC curves). Default key exchange scheme is "rsa". Ignored if -Y is "none". .TP .B \-c If specified, forces clients to authenticate by sending their RSA public key in a CLIENT_KEY message. Client key fingerprints and proxy key fingerprints specified by -H and -j respectively will NOT be checked unless -c is specified. Ignored if -Y is "none". .TP .B \-m max_nak_count Specifies the number of times a client reports naks beyond the maximum percentage before getting dropped. Valid values are 1-10. Default is 1. .TP .B \-k key_file .TP .B \-K key_length | curve These two options are used to read and/or write the server\(aqs RSA/ECDSA private key. Both are ignored if -Y is "none". The type of private key read/written depend on the key exchange algorithm chosen via the -e option. If -e is "rsa" or "ecdh_rsa", -K specifies the key length in bits of an RSA public/private keypair to generate, and -k expects an RSA key. If -e is "ecdh_ecdsa", -K specifies a named EC curve on which an EC public/private keypair is generated, and -k expects an EC key. The list of supported EC curves is as follows (availability may vary depending on system settings and crypto library used): sect163k1 sect163r1 sect163r2 sect193r1 sect193r2 sect233k1 sect233r1 sect239k1 sect283k1 sect283r1 sect409k1 sect409r1 sect571k1 sect571r1 secp160k1 secp160r1 secp160r2 secp192k1 prime192v1 secp224k1 secp224r1 secp256k1 prime256v1 secp384r1 secp521r1 If neither -k nor -K are specified, either an RSA private key 512 bits in length or an EC private key on curve prime256p1 (depending on the value of -e) is generated but not persisted. If -k is specified but not -K, the RSA or ECDSA private key is read from key_file. If -k is not specified but -K is, an RSA or ECDSA private key is generated but not persisted. If both -k and -K are specified, an RSA or ECDSA private key is generated and stored in key_file. The definition of key_file is dependent on the crypto library UFTP is compiled to use. On Windows systems, UFTP can built to use either CNG, which is the new API supported by Windows Vista and Windows 7, or CryptoAPI, which is the legacy API and the only one available to Windows XP. Under CryptoAPI, all RSA private keys must be stored in a key container (technically only keys used to sign data, but for UFTP\(aqs purposes this is the case). Key containers are internal to Windows, and each user (and the system) has its own set of key containers. In this case, key_file is actually the name of the key container. When -k is not specified, the generated key is not persisted. Elliptic Curve algorithms are not supported under CryptoAPI. Under CNG, RSA and ECDSA private keys are also stored in key containers, and RSA keys created by CrypoAPI may be read by CNG. Like CryptoAPI, key_file also specifies the key container name, and the generated key is not persisted if -k is not specified. CNG only supports 3 named EC curves: prime256v1, secp384r1, and secp521r1. All other systems use OpenSSL for the crypto library (although under Windows UFTP can be also be built to use it). In this case, key_file specifies a file name where the RSA private key is stored unencrypted in PEM format (the OS is expected to protect this file). When both -k and -K are specified, the file is only written to if it does not currently exist. If the file does exist, an error message will be returned and the server will exit. When -k is not specified, the generated key is not persisted. These PEM files may also be manipulated via the openssl(1) command line tool. Keys can also be generated and viewed via the uftp_keymgt(1) utility. .TP .B \-l Follow symbolic links. By default, if the server encounters a symbolic link, it will send the link itself instead of the file it points to. Specifying this flag causes the server to send the file the link points to. .TP .B \-T Print the timestamp on each line of output. If -L is specified, this option is implied. .TP .B \-b block_size Specifies the size of a data block. This value should be around 100-200 bytes less that the path MTU to provide ample room for all headers and extensions, up to and including the IP and UDP headers. Prior to version 4.0, this option specified the MTU and calculated the block size based on that. Default is 1300. .TP .B \-t ttl Specifies the time-to-live for multicast packets. Default is 1. .TP .B \-Q dscp Specifies the Differentiated Services Code Point (DSCP), formerly Type of Service (TOS), in the IP header for all outgoing packets. Valid values are 0-63 and may be specified in either decimal or hexadecimal. Default is 0. On Windows XP systems, the OS doesn\(aqt allow this parameter to be changed by default. To change this, add/modify the following DWORD registry value, set to 0, and reboot: HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DisableUserTOSSetting Not currently supported on Windows Vista or later. .TP .B \-z Enables sync mode. Clients will check if an incoming file exists. If so, the client will decline the incoming file if it either older than the existing file or the same age and the same size as the existing file. As of version 4.1, parsable output that was previously generated by this option is now enabled separately via the -S option. .TP .B \-Z Sync preview mode. Works like sync mode, except no files are actually transmitted, and the RESULT and STATS lines reflect the status of each file had they actually been sent. The "time" and "speed" datapoints are approximated based on the transmission speed. .TP .B \-I interface The interface to send the data from. Can be specified either by interface name, by hostname, or by IP. If not specified, the default system interface is used. .TP .B \-p port The UDP port number to send to. Default is 1044. .TP .B \-u source_port The UDP port number to send from. Default is 0, which uses a random port number. .TP .B \-j proxylist_file A file containing a list of proxies the server is expecting to hear from. The file should contain the ID of a proxy optionally followed by the proxy\(aqs public key fingerprint, with one on each line. If a key fingerprint is given, the key specified by the proxy must match the fingerprint. This option should not be used without -H. If -H is specified, -j must also be specified if proxies are expected to respond, otherwise the server will reject the proxies. .nf Example contents: 0x00001111|66:1E:C9:1D:FC:99:DB:60:B0:1A:F0:8F:CA:F4:28:27:A6:BE:94:BC 0x00002222 .fi .TP .B \-q Quit-on-error flag. Normally, the server will continue with a session as long as at least one client is still active. With this flag, the server will quit if any client aborts, drops out, or never responds. Most useful in conjunction with clients using the temp directory option (-T) so that clients that successfully receive at least one file before being told to abort don\(aqt have files from an aborted session in the destination directory. .TP .B \-f Restartable flag. If specified, and at least one client fails to receive all files, the server will write a restart file named "_group_{group ID}_restart in the current directory to save the current state, which includes the group ID, list of files, and list of failed clients. This file can then be passed to -F to restart the failed transfer. .TP .B \-y For Windows systems using CryptoAPI or CNG, private keys are normally stored in the key container of the running user. Specifying this option stores keys in the system key container. On non-Windows systems, this option has no effect. .TP .B \-x log_level Specifies current logging level. Valid values are 0-5, with 0 being the least verbose and 5 being the most verbose. Default is 2, which is consistent with logging prior to version 3.5. .TP .B \-W txweight Sets the maximum file transfer time, expressed as a percentage of the optimal time. Valid values are 110-10000. Ignored if congestion control is enabled. Default is no maximum time. .TP .B \-H { host[,host...] | @hostlist_file } Specifies the clients for closed group membership. Can be specified as either a comma separated list of client IDs, or can be read from hostlist_file. This file is in the same format as proxylist_file. Note that key fingerprints cannot be specified using the comma separated syntax. Clients that are behind a proxy do not need key fingerprints specified, since the proxy\(aqs key fingerprint will be checked instead. If unspecified, open group membership is used, and any client may register. .TP .B \-F restart_file Specifies the name of a restart file to use to resume a failed transfer. If specified, -H may not be specified and all files listed to send will be ignored, since the restart file contains both of these. All other command line options specified on the first attempt are not automatically applied, so you can alter then for the next attempt if need be. .TP .B \-X exclude_file A file containing the names of files/paths to be excluded from the session, one per line. For example, if you send a directory called d1 containing subdirectories d2, d3, and d4, and you don\(aqt want to send the contents of d4, the exclude_file should contain a line reading "d1/d4". .TP .B \-M pub_multicast_addr The public address to announce on. May be either a multicast address or a unicast address, and either IPv4 or IPv6. If a unicast address is specified, the -P option is ignored and all data moves over the specified unicast address. If a multicast IPv6 address is specified, -P must also be specified. Default is 230.4.4.1. .TP .B \-P priv_multicast_addr The private multicast address that the data is transferred to. One or more parts of the IP address (other that the first) may be replaced with the letter \(aqx\(aq, resulting in a random number being chosen for that part, either 0-255 for IPv4 or 0-0xFFFF for IPv6. Default value is 230.5.5.x. If clients are using source specific multicast (SSM), this and -M must specify valid SSM addresses, which fall in the range 232.0.0.0/8 for IPv4 and ff3x::/32 for IPv6 (here x specifies the multicast scope). The values for -M and -P must both be the same IP version. .TP .B \-N max_nak_pct Specifies the maximum percentage of NAKs that a client can report for a particular section. This option works with the -m option, which specifies the number of times a client may exceed this limit before getting dropped. This allows the server to keep a very slow client from stalling the session for others. Valid values are 0-100. Default is 100. .TP .B \-C cc_type Specifies the congestion control mode to use. Currently supported values are "none" and "tfmcc". Specifiying "none" means data will be sent at a fixed rate as specified by the -R option. Specifying "tfmcc" will use the TCP Friendly Multicast Congestion Control scheme as specified in RFC 4654. Default value is "none". The following is a description of how congestion control worked prior to version 4.0 and should be considered historical. Specifies a congestion control config file. Normally, the server always transmits at the speed specified by -R. With this option, the speed can be adjusted each time the server makes a request for NAKs from the clients based on the percentage of NAKs received to data packets sent. The file consists of one or more of the following lines: percentage;scaling_factor Where "percentage" is a whole number from 0-100 specifying a percentage of NAKs, and scaling_factor is a positive decimal number that the current sending rate is multiplied by for the given percentage. Entries must be listed in ascending order by percentage. If there is no entry for "100", the scaling factor for the last entry becomes the scaling factor for "100". When the server collects NAKs from the clients, it calculates the NAK percentage, then searches the congestion control entries in order for a percengage greater than or equal to the current NAK percentage, and adjusts the rate by the corresponding scaling factor. There may also be a single line specifying the maximum transmission speed: max;speed Where "speed" is the transmission speed in Kbps. If this entry is not specified, the maximum speed is the initial speed specified by -R. The congestion control config file is reread each time just before adjusting the rate. This allows environments which externally monitor the network to adjust the configuration on the fly. In the event of a failure to read the file, the last configuration successfully read is used. Here is a sample cc_config file: .nf max;10000 0;1.3 5;1.1 10;0.9 25;0.7 50;0.5 100;0.4 .fi .TP .B \-o .TP .B \-D dest_name These options specify the name given to the sent file(s) on the client side. If only one file/directory is specified to send and -o is not specified, the name spcified by -D is given to that file/directory, and the effects of -E are ignored. If more than one file/directory is specified to send, or if -o is specified, they are placed in a subdirectory with the name spcified by -D. This option may also specify an absolute path name. If so, clients must be either all Windows or all UNIX-like, since they have differing filesystem structures, otherwise the behavior is undefined. The server, however, need not be the same OS as the clients. When specifying an absolute path name, the path must be contained in one of a client\(aqs destination directories, otherwise the client will reject the file. When sending to Windows clients, an absolute path may be either local (drive:\\path\\to\\file) or remote (\\\\host\\share\\path\\to\\file). .TP .B -E base_dir[,base_dir...] Specifies one or more "base" directories for files. Normally, for any file/directory specified, any leading path elements are stripped from the name before sending. If the specified file/directory name matches one of the base directories, only the path elements of the base directory are stripped, and the remainder is sent as the file name. Any specified file/directory that does not match a base directory is skipped. For example, without -E, if you pass /path/to/file to send, the transmitted filename is file. If you pass in -E /path, the transmitted file name is to/file. .TP .B \-S status_file Prints easily parsable status information to a file. This information was previously only available in sync mode (-z) and was mixed with the normal logging output. The following is printed for each client after all have registered: CONNECT;status;target Where "status" is either "success" or "failed", and "target" is the name of the client. The following is printed after each file: RESULT;target;filename;size;status;speed Where "target" is the name of the client, "file" is the name of the current file, "size" is the size of the file in kilobytes (i.e. 1234KB), "speed" is the transmission speed for that file in KB/s, and status is: copy: The file was sent. overwrite: The file was sent, and overwrote an existing file. Only generated in sync mode. skipped: The file was declined by the client because it is older that the existing file. Only generated in sync mode. rejected: The file was rejected, because the file was sent with an absolute pathname and either the client is using a temp directory or the filename doesn\(aqt match one of the client\(aqs destination directories. The following is printed at the end of the session: STATS;target;num_copy;num_overwrite;num_skip;total_size;time;speed Where "target" is the name of the client, "num_copy" is the number of files sentwith "copy" status, "num_overwrite" is the number of files sent with "overwrite" status, "num_skip" is the number of files sent with "skipped" status, "total_size" is the total size of all files sent in kilobytes, "time" is the total transmission time for all files, and "speed" is the overall transmission speed for all files. Also, the following line is printed verbatim prior to the STATS lines for ease of reading: HSTATS;target;copy;overwrite;skip;totalKB;time;speedKB/s .TP .B \-r init_grtt[:min_grtt:max_grtt] Specifies the initial value, and optionally the min and max values, of the Group Round Trip Time (GRTT) used in timing calculations. The GRTT changes dynamically based on the network conditions. This option is useful if the initial connection period is to short or long, if receivers are getting bogged down and cannot respond to the server quick enough before timing out, or if receivers are getting flagged with too high of an RTT and take too long to recover to a resonable value. Valid values are 0.001 to 1000. Defaults are 0.5 for init_grtt, 0.01 for min_grtt, and 15.0 for max_grtt. .TP .B \-s robust Specifies the robustness factor for message retransmission. The server will resend particular messages up to robust times while waiting for client responses. Valid values are 10-50. Default is 20. .TP .B \-i list_file Name of a file containing a list of files to send, one per line. Empty lines are ignored. Passing in \(aq-\(aq for list_file reads files from stdin. Other files specified on the command line are ignored if -i is given. .TP .B file [ file...] The file(s) or directory(ies) to send. Any special files (block/character devices, pipes, sockets, etc.) are skipped. By default, any symbolic links are sent as links (see -l). Any Windows client will silently refuse to create them. If -F or -i is specified, any files listed will be ignored. There are also special metafile names that can send commands to the clients. The @DELETE:{filename} metafile instructs the client to delete the given filename. The usual rules regarding which of the client\(aqs destination directories to use also applies here. The @FREESPACE metafile will cause the client to report back the amount of free disk space in the primary destination directory. .SH EXAMPLES .P Starting with the default options: .RS 5 uftp the_file .RE The server sends the_file with no encryption at 1000 Kbps, sending announcements over 230.4.4.1 and later messages over 230.5.5.x (x is randomly selected). Any client that responds to the announcement will be accepted. The payload portion of the packets will be 1300 bytes. To send at 50 Mbps: .RS 5 uftp -R 50000 the_file .RE Or to allow the transmission rate to be determined dynamically: .RS 5 uftp -C tfmcc the_file .RE To send multiple files: .RS 5 uftp file_1 file_2 file_3 .RE or: .RS 5 uftp dir_1 dir_2 file_3 .RE To send multiple files that all land in a certain subdirectory on each client: .RS 5 uftp -D dest_dir file_1 file_2 .RE To send announcements over multicast address 224.1.2.3 and later messages over 224.4.5.6: .RS 5 uftp -M 224.1.2.3 -P 224.4.5.6 file .RE Or for IPv6: .RS 5 uftp -M ff02::1:2:3 -P ff02::4:5:6 file .RE Or in unicast mode: .RS 5 uftp -M host_or_ip file .RE Where host_or_ip is the hostname or unicast IP address of the host to send to. To send only to certain hosts: .RS 5 uftp -H client_id_1,client_id_2,client_id_3 file_to_send .RE or: .RS 5 uftp -H @file_containing_list_of_clients file_to_send .RE If you want to use jumbo ethernet frames of 9000 bytes (leaving 200 bytes of space for headers): .RS 5 uftp -b 8800 file_to_send .RE To send /path/to/file1 and /path/to/file2, and have them appear on clients as /remote/dir/to/file1 and /remote/dir/to/file2: .RS 5 uftp -E /path -D /remote/dir /path/to/file1 /path/to/file2 .RE To send a file encrypted with AES-256-CBC and SHA-1 hashing, using an autogenerated 512-bit RSA key to negotiate the session: .RS 5 uftp -Y aes256-cbc -h sha1 file_to_send .RE To do the above with a previously generated RSA key stored in key_file_or_container (under Windows, the name of an internal key container, otherwise the name of a file containing the key in PEM format): .RS 5 uftp -Y aes256-cbc -h sha1 -k key_file_or_container file_to_send .RE .SH SEE ALSO uftpd(1), uftpproxyd(1), uftp_keymgt(1) .SH NOTES .P The latest version of UFTP can be found at http://uftp-multicast.sourceforge.net. UFTP is covered by the GNU General Public License. Commercial licenses and support are available from Dennis Bush (bush@tcnj.edu). uftp-4.1.5/server_common.h0000644000076400007640000000466412250244021014534 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_COMMON_H #define _SERVER_COMMON_H #include "server.h" void set_uftp_header(struct uftp_h *header, int func, uint32_t group_id, uint8_t group_inst, double l_grtt, int l_gsize); void send_abort(const struct finfo_t *finfo, const char *message, const union sockaddr_u *destaddr, uint32_t dest, int encrypt, int current); int send_multiple(const struct finfo_t *finfo, unsigned char *packet, int message, int attempt, uint32_t *idlist, int state, int encrypt, const union sockaddr_u *destaddr, int regconf); int validate_packet(const unsigned char *packet, int len, const struct finfo_t *finfo); int sign_announce(const struct finfo_t *finfo, unsigned char *packet, int len); int find_client(uint32_t addr); int client_error(int listidx); void handle_abort(const unsigned char *message, int meslen, int idx, struct finfo_t *finfo, uint32_t src); int recalculate_grtt(const struct finfo_t *finfo, int grtt_set, int clear_measured); #endif // _SERVER_COMMON_H uftp-4.1.5/proxy_config.c0000644000076400007640000004552312250244021014356 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #ifdef WINDOWS #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #endif #include "proxy.h" #include "proxy_config.h" /** * Global command line values and sockets */ SOCKET listener; char pidfile[MAXPATHNAME]; char keyfile[MAXLIST][MAXPATHNAME], keyinfo[MAXLIST][MAXPATHNAME]; int proxy_type, debug, rcvbuf, dscp, keyfile_count, keyinfo_count; int hb_interval, priority; unsigned int ttl; char portname[PORTNAME_LEN], out_portname[PORTNAME_LEN]; int port, out_port; union sockaddr_u down_addr; int have_down_fingerprint; uint8_t down_fingerprint[HMAC_LEN]; uint32_t down_nonce, uid; union sockaddr_u hb_hosts[MAXLIST]; union sockaddr_u pub_multi[MAX_INTERFACES]; struct fp_list_t server_fp[MAXLIST], client_fp[MAXPROXYDEST]; struct iflist ifl[MAX_INTERFACES], m_interface[MAX_INTERFACES]; struct timeval next_hb_time, last_key_req; int ifl_len, hbhost_count, server_fp_count, client_fp_count; int keyfile_count, key_count, pub_multi_count, interface_count, sys_keys; struct iflist out_if; union key_t privkey[MAXLIST]; int privkey_type[MAXLIST]; union key_t dhkey; uint8_t ecdh_curve; struct pr_group_list_t group_list[MAXLIST]; extern char *optarg; extern int optind; /** * Adds a host and its fingerprint to the given list */ void add_hosts_by_name(struct fp_list_t *list, int *list_count, const char *filename, int expect_ip) { char line[1000], *hostid, *ipstr, *fingerprint; FILE *hostfile; struct addrinfo ai_hints, *ai_rval; uint32_t remote_uid; int rval; if ((hostfile = fopen(filename, "r")) == NULL) { fprintf(stderr,"Couldn't open server/client list %s: %s\n", filename, strerror(errno)); exit(1); } while (fgets(line, sizeof(line), hostfile)) { while (line[strlen(line)-1] == '\r' || line[strlen(line)-1] == '\n') { line[strlen(line)-1] = '\x0'; } if ((line[0] == '#') || (line[0] == '\x0')) { continue; } hostid = line; ipstr = strchr(hostid, '|'); if (ipstr) { *ipstr = '\x0'; ipstr++; if (expect_ip) { fingerprint = strchr(ipstr, '|'); if (fingerprint) { *fingerprint = '\x0'; fingerprint++; } } else { fingerprint = ipstr; ipstr = NULL; } } else { fingerprint = NULL; } if (strlen(hostid) >= DESTNAME_LEN) { fprintf(stderr, "Server/Client list %s: name too long\n", filename); exit(1); } remote_uid = strtoul(hostid, NULL, 16); if ((remote_uid == 0xffffffff) || (remote_uid == 0)) { fprintf(stderr, "Invalid UID %s\n", hostid); exit(1); } list[*list_count].uid = htonl(remote_uid); if (expect_ip) { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(ipstr, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid host name/address %s: %s\n", ipstr, gai_strerror(rval)); exit(1); } memcpy(&list[*list_count].addr, ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); } list[*list_count].has_fingerprint = parse_fingerprint(list[*list_count].fingerprint, fingerprint); (*list_count)++; } if (!feof(hostfile) && ferror(hostfile)) { perror("Failed to read from server/client list file"); exit(1); } fclose(hostfile); } /** * Set defaults for all command line arguments */ void set_defaults(void) { proxy_type = UNDEF_PROXY; hbhost_count = 0; memset(hb_hosts, 0, sizeof(hb_hosts)); hb_interval = DEF_HB_INT; debug = 0; log_level = DEF_LOG_LEVEL; strncpy(portname, DEF_PORT, sizeof(portname)-1); portname[sizeof(portname)-1] = '\x0'; port = atoi(portname); memset(&out_if, 0, sizeof(out_if)); ttl = DEF_TTL; dscp = DEF_DSCP; strncpy(out_portname, DEF_PORT, sizeof(out_portname)-1); out_portname[sizeof(out_portname)-1] = '\x0'; out_port = atoi(out_portname); uid = 0; rcvbuf = 0; strncpy(logfile, DEF_LOGFILE, sizeof(logfile)-1); logfile[sizeof(logfile)-1] = '\x0'; memset(pidfile, 0, sizeof(pidfile)); server_fp_count = 0; client_fp_count = 0; key_count = 0; keyfile_count = 0; keyinfo_count = 0; interface_count = 0; pub_multi_count = 0; sys_keys = 0; priority = 0; ecdh_curve = 0; max_log_size = 0; max_log_count = DEF_MAX_LOG_COUNT; } /** * Set argument defaults, read and validate command line options */ void process_args(int argc, char *argv[]) { struct addrinfo ai_hints, *ai_rval; int c, i, listidx, rval; long tmpval; char *p, *p2, *hoststr, *portstr, pubname[INET6_ADDRSTRLEN]; const char opts[] = "s:crdx:p:t:Q:N:O:U:q:mh:H:g:n:B:L:P:C:S:e:k:K:I:M:"; set_defaults(); srand((unsigned int)time(NULL) ^ getpid()); // read lettered arguments while ((c = getopt(argc, argv, opts)) != EOF) { switch (c) { case 's': if (proxy_type != UNDEF_PROXY) { fprintf(stderr, "Only one of -s, -c, -r may be specified\n"); exit(1); } proxy_type = SERVER_PROXY; memset(&down_addr, 0, sizeof(down_addr)); if (!strncmp(optarg, "fp=", 3)) { have_down_fingerprint = parse_fingerprint(down_fingerprint, optarg + 3); if (!have_down_fingerprint) { fprintf(stderr, "Failed to parse downstream fingerprint\n"); exit(1); } down_nonce = rand32(); } else { have_down_fingerprint = 0; memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(optarg, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid host name: %s: %s\n", optarg, gai_strerror(rval)); exit(1); } memcpy(&down_addr, ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); } break; case 'c': if (proxy_type != UNDEF_PROXY) { fprintf(stderr, "Only one of -s, -c, -r may be specified\n"); exit(1); } proxy_type = CLIENT_PROXY; break; case 'r': if (proxy_type != UNDEF_PROXY) { fprintf(stderr, "Only one of -s, -c, -r may be specified\n"); exit(1); } proxy_type = RESPONSE_PROXY; break; case 'd': debug = 1; break; case 'x': log_level = atoi(optarg); if (log_level < 0) { fprintf(stderr, "Invalid log level\n"); exit(1); } break; case 'p': strncpy(portname, optarg, sizeof(portname)-1); portname[sizeof(portname)-1] = '\x0'; port = atoi(portname); if (port == 0) { fprintf(stderr, "Invalid port\n"); exit(1); } break; case 't': tmpval = atoi(optarg); if ((tmpval <= 0) || (tmpval > 255)) { fprintf(stderr, "Invalid ttl\n"); exit(1); } ttl = (char)tmpval; break; case 'Q': tmpval = strtol(optarg, NULL, 0); if ((tmpval < 0) || (tmpval > 63)) { fprintf(stderr, "Invalid dscp\n"); exit(1); } dscp = (tmpval & 0xFF) << 2; break; case 'N': priority = atoi(optarg); if (!valid_priority(priority)) { fprintf(stderr, "Invalid priority value\n"); exit(1); } break; case 'O': if ((listidx = getifbyname(optarg, ifl, ifl_len)) != -1) { out_if = ifl[listidx]; break; } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(optarg, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid name/address %s: %s\n", optarg, gai_strerror(rval)); exit(1); } // Just use the first addrinfo entry if ((listidx = getifbyaddr((union sockaddr_u *)ai_rval->ai_addr, ifl, ifl_len)) == -1) { fprintf(stderr, "Interface %s not found", optarg); exit(1); } out_if = ifl[listidx]; freeaddrinfo(ai_rval); break; case 'U': errno = 0; uid = strtoul(optarg, NULL, 16); if (errno) { perror("Invalid UID\n"); exit(1); } uid = htonl(uid); break; case 'q': strncpy(out_portname, optarg, sizeof(out_portname)-1); out_portname[sizeof(out_portname)-1] = '\x0'; out_port = atoi(out_portname); if (out_port == 0) { fprintf(stderr, "Invalid outgoing port\n"); exit(1); } break; case 'm': sys_keys = 1; break; case 'H': p = strtok(optarg, ","); while (p != NULL) { p2 = strchr(p, ':'); if (p2) { hoststr = strdup(p); hoststr[p2 - p] = '\x0'; portstr = p2 + 1; } else { hoststr = p; portstr = NULL; } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(hoststr, portstr ? portstr : DEF_PORT, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid name/address %s: %s\n", hoststr, gai_strerror(rval)); exit(1); } memcpy(&hb_hosts[hbhost_count++], ai_rval->ai_addr, ai_rval->ai_addrlen); p = strtok(NULL, ","); } break; case 'h': hb_interval = atoi(optarg); if ((hb_interval <= 0) || (hb_interval > 3600)) { fprintf(stderr, "Invalid hearbeat interval\n"); exit(1); } break; case 'g': max_log_size = atoi(optarg); if ((max_log_size < 1) || (max_log_size > 1024)) { fprintf(stderr, "Invalid max log size\n"); exit(1); } max_log_size *= 1000000; break; case 'n': max_log_count = atoi(optarg); if ((max_log_count < 1) || (max_log_count > 1000)) { fprintf(stderr, "Invalid max log count\n"); exit(1); } break; case 'B': rcvbuf = atoi(optarg); if ((rcvbuf < 65536) || (rcvbuf > 104857600)) { fprintf(stderr, "Invalid buffer size\n"); exit(1); } break; case 'L': strncpy(logfile, optarg, sizeof(logfile)-1); logfile[sizeof(logfile)-1] = '\x0'; break; case 'P': strncpy(pidfile, optarg, sizeof(pidfile)-1); pidfile[sizeof(pidfile)-1] = '\x0'; break; case 'C': add_hosts_by_name(client_fp, &client_fp_count, optarg, 0); break; case 'S': add_hosts_by_name(server_fp, &server_fp_count, optarg, 1); break; case 'e': ecdh_curve = get_curve(optarg); if (ecdh_curve == 0) { fprintf(stderr, "Invalid curve\n"); exit(1); } break; case 'k': p = strtok(optarg, ","); while (p != NULL) { strncpy(keyfile[keyfile_count], p, sizeof(keyfile[0])-1); keyfile[keyfile_count][sizeof(keyfile[0])-1] = '\x0'; keyfile_count++; p = strtok(NULL, ","); } break; case 'K': p = strtok(optarg, ","); while (p != NULL) { strncpy(keyinfo[keyinfo_count], p, sizeof(keyinfo[0])-1); keyinfo[keyinfo_count][sizeof(keyinfo[0])-1] = '\x0'; keyinfo_count++; p = strtok(NULL, ","); } break; case 'I': p = strtok(optarg, ","); while (p != NULL) { if ((listidx = getifbyname(p, ifl, ifl_len)) != -1) { m_interface[interface_count++] = ifl[listidx]; p = strtok(NULL, ","); continue; } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(p, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid name/address %s: %s\n", p, gai_strerror(rval)); exit(1); } if ((listidx = getifbyaddr((union sockaddr_u *)ai_rval->ai_addr, ifl, ifl_len)) == -1) { fprintf(stderr, "Interface %s not found\n", p); exit(1); } m_interface[interface_count++] = ifl[listidx]; freeaddrinfo(ai_rval); p = strtok(NULL, ","); } break; case 'M': p = strtok(optarg, ","); while (p != NULL) { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(p, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid multicast address %s: %s\n", p, gai_strerror(rval)); exit(1); } memcpy(&pub_multi[pub_multi_count], ai_rval->ai_addr, ai_rval->ai_addrlen); pub_multi_count++; freeaddrinfo(ai_rval); p = strtok(NULL, ","); } break; case '?': fprintf(stderr, USAGE); exit(1); } } if (proxy_type == UNDEF_PROXY) { fprintf(stderr, "Either -s, -c, or -r must be specified\n"); fprintf(stderr, USAGE); exit(1); } if (proxy_type == RESPONSE_PROXY) { out_port = port; } if (proxy_type == SERVER_PROXY) { if (down_addr.ss.ss_family == AF_INET6) { down_addr.sin6.sin6_port = htons(out_port); } else { down_addr.sin.sin_port = htons(out_port); } } if (proxy_type != CLIENT_PROXY) { if (server_fp_count) { for (i = 0; i < pub_multi_count; i++) { if (!is_multicast(&pub_multi[i], 1)) { if ((rval = getnameinfo((struct sockaddr *)&pub_multi[i], family_len(pub_multi[i]), pubname, sizeof(pubname), NULL, 0, NI_NUMERICHOST)) != 0) { fprintf(stderr,"getnameinfo failed: %s", gai_strerror(rval)); } fprintf(stderr, "Invalid source specific " "multicast address: %s\n", pubname); exit(1); } } if (pub_multi_count == 0) { fprintf(stderr, "Default multicast address %s invalid " "for source specific multicast\n", DEF_PUB_MULTI); exit(1); } } } if ((keyfile_count != 0) && (keyinfo_count != 0) && (keyfile_count != keyinfo_count)) { fprintf(stderr, "Must list same number of items for -k and -K\n"); exit(1); } for (i = 0; i < pub_multi_count; i++) { if (pub_multi[i].ss.ss_family == AF_INET6) { pub_multi[i].sin6.sin6_port = htons(out_port); } else { pub_multi[i].sin.sin_port = htons(out_port); } } } uftp-4.1.5/uftp_common.h0000644000076400007640000002052712250244021014200 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _UFTP_COMMON_H #define _UFTP_COMMON_H #include #ifdef WINDOWS #include #include #else #include #endif #include "uftp.h" #include "encryption.h" #define DEF_LOG_LEVEL 2 #define DEF_MAX_LOG_COUNT 5 extern char logfile[MAXPATHNAME]; extern int showtime; extern FILE *applog; extern int log_level, init_log_mux, use_log_mux, max_log_count; extern f_offset_t log_size, max_log_size; extern mux_t log_mux; void init_log(int _debug); void close_log(void); void roll_log(void); void logfunc(uint32_t group_id, uint16_t file_id, int level, int _showtime, int newline, int err, int sockerr, const char *str, ...); #define clog0(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 0, 0, 0, __VA_ARGS__) #define log0(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 1, 0, 0, __VA_ARGS__) #define sclog0(...) \ logfunc(0, 0, 0, 0, 0, 0, 0, __VA_ARGS__) #define slog0(...) \ logfunc(0, 0, 0, 0, 1, 0, 0, __VA_ARGS__) #define clog1(group_id, file_id, ...) \ logfunc(group_id, file_id, 1, showtime, 0, 0, 0, __VA_ARGS__) #define log1(group_id, file_id, ...) \ logfunc(group_id, file_id, 1, showtime, 1, 0, 0, __VA_ARGS__) #define sclog1(...) \ logfunc(0, 0, 1, 0, 0, 0, 0, __VA_ARGS__) #define slog1(...) \ logfunc(0, 0, 1, 0, 1, 0, 0, __VA_ARGS__) #define clog2(group_id, file_id, ...) \ logfunc(group_id, file_id, 2, showtime, 0, 0, 0, __VA_ARGS__) #define log2(group_id, file_id, ...) \ logfunc(group_id, file_id, 2, showtime, 1, 0, 0, __VA_ARGS__) #define sclog2(...) \ logfunc(0, 0, 2, 0, 0, 0, 0, __VA_ARGS__) #define slog2(...) \ logfunc(0, 0, 2, 0, 1, 0, 0, __VA_ARGS__) #define clog3(group_id, file_id, ...) \ logfunc(group_id, file_id, 3, showtime, 0, 0, 0, __VA_ARGS__) #define log3(group_id, file_id, ...) \ logfunc(group_id, file_id, 3, showtime, 1, 0, 0, __VA_ARGS__) #define sclog3(...) \ logfunc(0, 0, 3, 0, 0, 0, 0, __VA_ARGS__) #define slog3(...) \ logfunc(0, 0, 3, 0, 1, 0, 0, __VA_ARGS__) #define clog4(group_id, file_id, ...) \ logfunc(group_id, file_id, 4, showtime, 0, 0, 0, __VA_ARGS__) #define log4(group_id, file_id, ...) \ logfunc(group_id, file_id, 4, showtime, 1, 0, 0, __VA_ARGS__) #define sclog4(...) \ logfunc(0, 0, 4, 0, 0, 0, 0, __VA_ARGS__) #define slog4(...) \ logfunc(0, 0, 4, 0, 1, 0, 0, __VA_ARGS__) #define clog5(group_id, file_id, ...) \ logfunc(group_id, file_id, 5, showtime, 0, 0, 0, __VA_ARGS__) #define log5(group_id, file_id, ...) \ logfunc(group_id, file_id, 5, showtime, 1, 0, 0, __VA_ARGS__) #define sclog5(...) \ logfunc(0, 0, 5, 0, 0, 0, 0, __VA_ARGS__) #define slog5(...) \ logfunc(0, 0, 5, 0, 1, 0, 0, __VA_ARGS__) #define syserror(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 1, errno, 0, __VA_ARGS__) #define sockerror(group_id, file_id, ...) \ logfunc(group_id, file_id, 0, showtime, 1, errno, 1, __VA_ARGS__) union sockaddr_u { struct sockaddr_storage ss; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; struct iflist { char name[IFNAME_LEN]; union sockaddr_u su; int isloopback; int ismulti; int ifidx; }; const char *func_name(int func); const char *curve_name(int curve); uint8_t get_curve(const char *name); int32_t diff_sec(struct timeval t2, struct timeval t1); int64_t diff_usec(struct timeval t2, struct timeval t1); int cmptimestamp(struct timeval t1, struct timeval t2); struct timeval add_timeval(struct timeval t2, struct timeval t1); void add_timeval_d(struct timeval *t2, double t1); struct timeval diff_timeval(struct timeval t2, struct timeval t1); const char *printll(int64_t n); void getiflist(struct iflist *list, int *len); void split_path(const char *path, char **dir, char **file); int parse_fingerprint(unsigned char *fingerprint, const char *fingerprint_str); int is_multicast(const union sockaddr_u *addr, int ssm); int addr_equal(const union sockaddr_u *addr1, const union sockaddr_u *addr2); int addr_blank(const union sockaddr_u *addr); uint64_t uftp_htonll(uint64_t val); uint64_t uftp_ntohll(uint64_t val); int family_len(union sockaddr_u addr); int would_block_err(void); int nb_sendto(SOCKET s, const void *msg, int len, int flags, const struct sockaddr *to, int tolen); int read_packet(SOCKET sock, union sockaddr_u *sa, unsigned char *buffer, int *len, int bsize, const struct timeval *timeout); void build_iv(uint8_t *iv, const uint8_t *salt, int ivlen, uint64_t ivctr, uint32_t src_id); void printhex(const char *name, const unsigned char *data, int len); int is_auth_enc(int keytype); int is_gcm_mode(int keytype); int is_ccm_mode(int keytype); int unauth_key(int keytype); int encrypt_and_sign(const unsigned char *decpacket, unsigned char **encpacket, int declen, int *enclen, int keytype, uint8_t *key, const uint8_t *salt, uint64_t *ivctr, int ivlen, int hashtype, uint8_t *hmackey, uint8_t hmaclen, int sigtype, int keyextype, union key_t privkey, int privkeylen); int validate_and_decrypt(unsigned char *encpacket, unsigned int enclen, unsigned char **decpacket, unsigned int *declen, int keytype, const uint8_t *key, const uint8_t *salt, int ivlen, int hashtype, const uint8_t *hmackey, uint8_t hmaclen, int sigtype, int keyextype, union key_t pubkey, int pubkeylen); void PRF(int hashtype, int bytes, const unsigned char *secret, int secret_len, const char *label, const unsigned char *seed, int seed_len, unsigned char *outbuf, int *outbuf_len); const char *print_key_fingerprint(const union key_t key, int keytype); /** * Key fingerprint for an allowed server or client */ struct fp_list_t { uint32_t uid; union sockaddr_u addr; int has_fingerprint; uint8_t fingerprint[HMAC_LEN]; }; int multicast_join(SOCKET s, uint32_t group_id, const union sockaddr_u *multi, const struct iflist *addrlist, int addrlen, const struct fp_list_t *fplist, int fplist_len); void multicast_leave(SOCKET s, uint32_t group_id, const union sockaddr_u *multi, const struct iflist *addrlist, int addrlen, const struct fp_list_t *fplist, int fplist_len); int getifbyname(const char *name, const struct iflist *list, int len); int getifbyaddr(union sockaddr_u *su, const struct iflist *list, int len); int file_read(int fd, void *buf, int buflen, int allow_eof); int file_write(int fd, const void *buf, int buflen); int64_t free_space(const char *dir); int valid_priority(int priority); uint32_t rand32(void); void *safe_malloc(size_t size); void *safe_calloc(size_t num, size_t size); uint8_t quantize_grtt(double rtt); double unquantize_grtt(uint8_t rtt); uint8_t quantize_gsize(int size); int unquantize_gsize(uint8_t size); uint16_t quantize_rate(uint32_t size); uint32_t unquantize_rate(uint16_t size); #endif // _UFTP_COMMON_H uftp-4.1.5/proxy_loop.c0000644000076400007640000003424612304524207014071 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2014 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include "win_func.h" #else // WINDOWS #include #include #include #include #include #endif #include "proxy.h" #include "proxy_common.h" #include "proxy_loop.h" #include "proxy_upstream.h" #include "proxy_downstream.h" #include "heartbeat_send.h" /** * 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, j, found_timeout, recheck, pending; struct pr_group_list_t *group; int32_t usecs; gettimeofday(¤t_timestamp, NULL); recheck = 1; while (recheck) { found_timeout = 0; recheck = 0; // First check group timeouts for (i = 0; i < MAXLIST; i++) { group = &group_list[i]; if (group->group_id != 0) { if ((group->phase == PR_PHASE_REGISTERED) || (group->phase == PR_PHASE_READY)) { if ((group->phase == PR_PHASE_READY) && (cmptimestamp(current_timestamp, group->phase_timeout_time) >= 0)) { send_keyinfo(group, NULL, 0); recheck = 1; } if (cmptimestamp(current_timestamp, group->phase_expire_time) >= 0) { group->phase = PR_PHASE_RECEIVING; check_unfinished_clients(group, 1); } } if (cmptimestamp(current_timestamp, group->timeout_time) >= 0) { // If at least one message is pending, timeout_time is // time to next send of the specified message. // Otherwise it's the overall timeout. log5(group->group_id, 0, "timeout, checking pending"); for (pending = 0, j = 0; (j < MAX_PEND) && !pending; j++) { if (group->pending[j].msg != 0) { log5(group->group_id, 0, "found pending %s", func_name(group->pending[j].msg)); pending = 1; } } if (pending) { send_all_pending(group); } else { log1(group->group_id, 0, "Group timed out"); group_cleanup(group); } recheck = 1; } if (!recheck && ((!found_timeout) || (cmptimestamp(group->timeout_time, min_timestamp) < 0))) { min_timestamp = group->timeout_time; found_timeout = 1; } } } // Then 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); recheck = 1; } else if ((!found_timeout) || (cmptimestamp(next_hb_time, min_timestamp) < 0)) { min_timestamp = next_hb_time; found_timeout = 1; } } } if (found_timeout) { usecs = (int32_t)diff_usec(min_timestamp, current_timestamp); tv.tv_sec = usecs / 1000000; tv.tv_usec = usecs % 1000000; 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 pr_group_list_t *group; unsigned char *buf, *decrypted, *message; char rxname[INET6_ADDRSTRLEN]; int packetlen, rval, hostidx, i; unsigned int decryptlen, meslen; uint8_t *func; union sockaddr_u src; struct timeval *tv; double new_grtt; log0(0, 0, "%s", VERSIONSTR); for (i = 0; i < key_count; i++) { if (privkey_type[i] == KEYBLOB_RSA) { log1(0, 0, "Loaded %d bit RSA key with fingerprint %s", RSA_keylen(privkey[i].rsa) * 8, print_key_fingerprint(privkey[i], KEYBLOB_RSA)); } else { log1(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, "timeout: %d.%06d", tv->tv_sec, tv->tv_usec); } if (read_packet(listener, &src, buf, &packetlen, MAXMTU, tv) <= 0) { continue; } if ((rval = getnameinfo((struct sockaddr *)&src, family_len(src), rxname, sizeof(rxname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } if (header->version != UFTP_VER_NUM) { log2(0, 0, "Invalid message from %s: not uftp packet " "or invalid version", rxname); continue; } if (packetlen < sizeof(struct uftp_h) + 4) { log1(0, 0, "Invalid packet size from %s: %d", rxname, packetlen); continue; } if (addr_equal(&src, &out_if.su)) { // Packet from self -- drop continue; } if (header->func == HB_REQ) { handle_hb_request(&src, buf, packetlen); continue; } if (header->func == HB_RESP) { handle_hb_response(listener, &src, buf + sizeof(struct uftp_h), packetlen - sizeof(struct uftp_h), hb_hosts, hbhost_count, privkey[0], privkey_type[0], uid); continue; } if (header->func == KEY_REQ) { handle_key_req(&src, buf, packetlen); continue; } if (header->func == PROXY_KEY) { // Only clients handle these, so drop continue; } if ((proxy_type == SERVER_PROXY) && (addr_blank(&down_addr))) { log2(0, 0, "Rejecting message from %s: downstream address " "not established", rxname); continue; } group = find_group(ntohl(header->group_id), header->group_inst); if (header->func == ANNOUNCE) { handle_announce(group, &src, buf, packetlen); } else { if (group == NULL) { continue; } if (group->version != header->version) { log1(group->group_id, 0, "Version mismatch"); continue; } if (proxy_type == SERVER_PROXY) { // Server proxies don't do anything outside of an ANNOUNCE. // Just send it on through. if (!memcmp(&src, &group->up_addr, sizeof(src))) { new_grtt = unquantize_grtt(header->grtt); if (fabs(new_grtt - group->grtt) > 0.001) { group->grtt = new_grtt; set_timeout(group, 0, 1); } group->gsize = unquantize_gsize(header->gsize); log4(group->group_id, 0, "grtt: %.3f", group->grtt); } forward_message(group, &src, buf, packetlen); continue; } if (!memcmp(&src, &group->up_addr, sizeof(src))) { // Downstream message if (group->src_id != header->src_id) { log1(group->group_id, 0, "Source ID mismatch"); continue; } new_grtt = unquantize_grtt(header->grtt); if (fabs(new_grtt - group->grtt) > 0.001) { group->grtt = new_grtt; set_timeout(group, 0, 1); } group->gsize = unquantize_gsize(header->gsize); log4(group->group_id, 0, "grtt: %.3f", group->grtt); message = buf + sizeof(struct uftp_h); meslen = packetlen - sizeof(struct uftp_h); if (header->func == ABORT) { handle_abort(group, &src, message, meslen, header->src_id); } else if (header->func == KEYINFO) { handle_keyinfo(group, message, meslen, header->src_id); } else if ((header->func == REG_CONF) && (group->keytype != KEY_NONE)) { handle_regconf(group, message, meslen); } else { // If we don't need to process the message, don't bother // decrypting anything. Just forward it on. forward_message(group, &src, buf, packetlen); } } else { // Upstream message // Decrypt first if necessary hostidx = find_client(group, header->src_id); if ((hostidx == -1) && (header->func != REGISTER) && (header->func != CLIENT_KEY)) { log1(0, 0, "Host %08X not in host list", ntohl(header->src_id)); continue; } if ((hostidx != -1) && (header->func == ENCRYPTED) && (group->keytype != KEY_NONE)) { 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->destinfo[hostidx].pubkey, group->destinfo[hostidx].pubkeylen)) { log2(ntohl(header->group_id), 0, "Rejecting message " "from %s: decrypt/validate failed", rxname); continue; } func = (uint8_t *)decrypted; message = decrypted; meslen = decryptlen; } else { if ((hostidx != -1) && (group->keytype != KEY_NONE) && ((header->func == KEYINFO_ACK) || (header->func == FILEINFO_ACK) || (header->func == STATUS) || (header->func == COMPLETE))) { log2(ntohl(header->group_id), 0, "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); } switch (*func) { case REGISTER: handle_register(group, hostidx, message, meslen, header->src_id); break; case CLIENT_KEY: handle_clientkey(group, hostidx, message, meslen, header->src_id); break; case KEYINFO_ACK: handle_keyinfo_ack(group, hostidx, message, meslen); break; case FILEINFO_ACK: handle_fileinfo_ack(group, hostidx, message, meslen); break; case STATUS: handle_status(group, hostidx, message, meslen); break; case COMPLETE: handle_complete(group, hostidx, message, meslen); break; case ABORT: handle_abort(group, &src, message, meslen, header->src_id); break; default: forward_message(group, &src, buf, packetlen); break; } } } } } uftp-4.1.5/server_announce.c0000644000076400007640000012324412250244021015041 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #endif #include "server.h" #include "server_common.h" #include "server_announce.h" /** * 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)) { log0(finfo->group_id, finfo->file_id, "Error exporting server public key"); return 0; } } else { if (!export_EC_key(privkey.ec, keyblob, &bloblen)) { log0(finfo->group_id, finfo->file_id, "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)) { log0(finfo->group_id, finfo->file_id, "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; } /** * 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) { log0(finfo->group_id, finfo->file_id, "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)) { log0(finfo->group_id, finfo->file_id, "Error signing ANNOUNCE"); free(buf); return 0; } log2(finfo->group_id, finfo->file_id, "Sending ANNOUNCE %d", attempt); if (nb_sendto(sock, buf, packetlen, 0, (struct sockaddr *)&listen_dest, family_len(listen_dest)) == SOCKET_ERROR) { sockerror(finfo->group_id,finfo->file_id, "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; } } /** * Send out REG_CONF messages specifiying all registered clients. * Sent when encryption is disabled, or if the client is behind a proxy. * Returns 1 on success, 0 on fail */ int send_regconf(const struct finfo_t *finfo, int attempt, int do_regconf) { int rval; unsigned char *buf; struct uftp_h *header; struct regconf_h *regconf; uint32_t *idlist; buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; regconf = (struct regconf_h *)(buf + sizeof(struct uftp_h)); set_uftp_header(header, REG_CONF, finfo->group_id, finfo->group_inst, grtt, destcount); regconf->func = REG_CONF; regconf->hlen = sizeof(struct regconf_h) / 4; idlist = (uint32_t *)((uint8_t *)regconf + (regconf->hlen * 4)); rval = send_multiple(finfo, buf, REG_CONF, attempt, idlist, DEST_ACTIVE, 0, &receive_dest, do_regconf); free(buf); return rval; } /** * 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)) { log0(finfo->group_id, finfo->file_id, "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)); log2(finfo->group_id, finfo->file_id, "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) { sockerror(finfo->group_id, finfo->file_id, "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++; } } free(buf); free(iv); return 1; } /** * Send a FILEINFO message. Sent for each individual file. * Returns 1 on success, 0 on fail. */ int send_fileinfo(const struct finfo_t *finfo, int attempt) { int rval; unsigned char *buf; struct uftp_h *header; struct fileinfo_h *fileinfo; struct timeval tv; uint32_t *idlist; char *filename, *linkname; if (strlen(finfo->destfname) > MAXPATHNAME) { log0(finfo->group_id, finfo->file_id, "File name too long: %s", finfo->destfname); return 0; } buf = safe_calloc(MAXMTU, 1); header = (struct uftp_h *)buf; fileinfo = (struct fileinfo_h *)(buf + sizeof(struct uftp_h)); filename = (char *)fileinfo + sizeof(struct fileinfo_h); set_uftp_header(header, FILEINFO, finfo->group_id, finfo->group_inst, grtt, destcount); fileinfo->func = FILEINFO; fileinfo->ftype = finfo->ftype; fileinfo->file_id = htons(finfo->file_id); fileinfo->namelen = (uint8_t)(0 + ceil(strlen(finfo->destfname) / 4.0)); fileinfo->lofsize = htonl((finfo->size & 0xFFFFFFFF)); fileinfo->hifsize = htons((uint16_t)(finfo->size >> 32)); if (sync_mode) { fileinfo->ftstamp = htonl(finfo->tstamp); } else { fileinfo->ftstamp = 0; } gettimeofday(&tv, NULL); fileinfo->tstamp_sec = htonl(tv.tv_sec); fileinfo->tstamp_usec = htonl(tv.tv_usec); strncpy(filename, finfo->destfname, MAXPATHNAME); if (finfo->ftype == FTYPE_LINK) { if (strlen(finfo->linkname) > (unsigned)MAXPATHNAME - (fileinfo->namelen * 4)) { log0(finfo->group_id, finfo->file_id, "Link name too long: %s", finfo->linkname); free(buf); return 0; } linkname = filename + (fileinfo->namelen * 4); strncpy(linkname, finfo->linkname, MAXPATHNAME - (fileinfo->namelen * 4)); fileinfo->linklen = (uint8_t)(0 + ceil(strlen(finfo->linkname) / 4.0)); } fileinfo->hlen = (sizeof(struct fileinfo_h) + (fileinfo->namelen * 4) + (fileinfo->linklen * 4)) / 4; idlist = (uint32_t *)((uint8_t *)fileinfo + (fileinfo->hlen * 4)); rval = send_multiple(finfo, buf, FILEINFO, attempt, idlist, DEST_REGISTERED, (keytype != KEY_NONE), &receive_dest, 0); free(buf); return rval; } /** * Adds a registered host to the hostlist. Returns the list index. */ int add_dest_by_addr(uint32_t id, struct finfo_t *finfo, int state, int proxyidx, int clientcnt) { snprintf(destlist[destcount].name, sizeof(destlist[destcount].name), "0x%08X", ntohl(id)); destlist[destcount].id = id; destlist[destcount].status = state; destlist[destcount].proxyidx = proxyidx; destlist[destcount].clientcnt = clientcnt; return destcount++; } /** * When a proxy registers, process the clients the proxy is serving */ void add_proxy_dests(struct finfo_t *finfo, const uint32_t *idlist, const union sockaddr_u *su, int clientcnt, int proxyidx, int open, double rtt) { int startcnt, addcnt, hostidx, i, dupmsg; if (destlist[proxyidx].clientcnt == -1) { // True when using open group membership and // we get a CLIENT_KEY before the REGSITER for a proxy destlist[proxyidx].clientcnt = 0; } if (destlist[proxyidx].clients == NULL) { destlist[proxyidx].clients = safe_calloc(MAXPROXYDEST, sizeof(*destlist[proxyidx].clients)); } startcnt = destlist[proxyidx].clientcnt; for (addcnt = 0, i = 0; i < clientcnt; i++) { dupmsg = 0; hostidx = find_client(idlist[i]); if (hostidx == -1) { if (open) { if (destcount == MAXDEST) { log1(finfo->group_id, finfo->file_id, "Rejecting client %08X: max destinations exceeded", ntohl(idlist[i])); send_abort(finfo, "Max destinations exceeded", su, idlist[i], 0, 0); continue; } if (startcnt + addcnt == MAXPROXYDEST) { log1(finfo->group_id, finfo->file_id, "Rejecting client %08X: max destinations " "exceeded for proxy", idlist[i]); send_abort(finfo, "Max destinations exceeded for proxy", su, idlist[i], 0, 0); continue; } hostidx = add_dest_by_addr(idlist[i], finfo, DEST_ACTIVE, proxyidx, -1); } else { log1(finfo->group_id, finfo->file_id, "Host %08X not in host list", idlist[i]); send_abort(finfo, "Not in host list", su, idlist[i], 0, 0); continue; } } else { dupmsg = (destlist[hostidx].status == DEST_ACTIVE); destlist[hostidx].status = DEST_ACTIVE; destlist[hostidx].proxyidx = proxyidx; } destlist[hostidx].rtt = rtt; finfo->deststate[hostidx].conf_sent = 0; log1(finfo->group_id, finfo->file_id, " For client%s %s", dupmsg ? "+" : "", destlist[hostidx].name); destlist[proxyidx].clients[addcnt + startcnt] = hostidx; addcnt++; } destlist[proxyidx].clientcnt += addcnt; } /** * Returns the verify_data string used in certain messages. This value * is then run through the PRF with the result going into the message */ uint8_t *build_verify_data(const struct finfo_t *finfo, int hostidx, int *verifylen) { uint8_t *verifydata; uint8_t privatemcast[16]; uint32_t n_group_id; int iplen; if (listen_dest.ss.ss_family == AF_INET6) { iplen = sizeof(struct in6_addr); } else { iplen = sizeof(struct in_addr); } if (!is_multicast(&listen_dest, 0)) { memset(privatemcast, 0, iplen); } else if (listen_dest.ss.ss_family == AF_INET6) { memcpy(privatemcast, &receive_dest.sin6.sin6_addr.s6_addr, iplen); } else { memcpy(privatemcast, &receive_dest.sin.sin_addr.s_addr, iplen); } *verifylen = 0; if (destlist[hostidx].status == DEST_MUTE) { verifydata = safe_calloc(sizeof(finfo->group_id) + iplen + sizeof(rand1) + sizeof(destlist[hostidx].encinfo->rand2) + sizeof(destlist[hostidx].encinfo->premaster), 1); } else { verifydata = safe_calloc(sizeof(finfo->group_id) + iplen + sizeof(rand1) + sizeof(destlist[hostidx].encinfo->rand2) + sizeof(destlist[hostidx].encinfo->premaster) + PUBKEY_LEN + sizeof(groupmaster), 1); } n_group_id = htonl(finfo->group_id); memcpy(verifydata, &n_group_id, sizeof(n_group_id)); *verifylen += sizeof(n_group_id); memcpy(verifydata + *verifylen, &privatemcast, iplen); *verifylen += iplen; memcpy(verifydata + *verifylen, rand1, sizeof(rand1)); *verifylen += sizeof(rand1); memcpy(verifydata + *verifylen, destlist[hostidx].encinfo->rand2, sizeof(destlist[hostidx].encinfo->rand2)); *verifylen += sizeof(destlist[hostidx].encinfo->rand2); memcpy(verifydata + *verifylen, destlist[hostidx].encinfo->premaster, destlist[hostidx].encinfo->premaster_len); *verifylen += destlist[hostidx].encinfo->premaster_len; if (destlist[hostidx].status != DEST_MUTE) { if (destlist[hostidx].encinfo->pubkey.key) { uint16_t bloblen; uint8_t *keyblob = verifydata + *verifylen; if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { if (!export_RSA_key(destlist[hostidx].encinfo->pubkey.rsa, keyblob, &bloblen)) { free(verifydata); return NULL; } } else { if (!export_EC_key(destlist[hostidx].encinfo->pubkey.ec, keyblob, &bloblen)) { log0(finfo->group_id, finfo->file_id, "Error exporting server public key"); free(verifydata); return NULL; } } *verifylen += bloblen; } memcpy(verifydata + *verifylen, groupmaster, sizeof(groupmaster)); *verifylen += sizeof(groupmaster); } return verifydata; } /** * Verifies the data in a CLIENT_KEY message signed by the client's public key */ int verify_client_key(struct finfo_t *finfo, int hostidx) { uint8_t *verifydata; int verifylen; // build_verify_data should never fail in this case verifydata = build_verify_data(finfo, hostidx, &verifylen); if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { if (!verify_RSA_sig(destlist[hostidx].encinfo->pubkey.rsa, hashtype, verifydata, verifylen, destlist[hostidx].encinfo->verifydata, destlist[hostidx].encinfo->verifylen)) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY " "from %s: verify data mismatch", destlist[hostidx].name); free(verifydata); return 0; } } else { if (!verify_ECDSA_sig(destlist[hostidx].encinfo->pubkey.ec, hashtype, verifydata, verifylen, destlist[hostidx].encinfo->verifydata, destlist[hostidx].encinfo->verifylen)) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY " "from %s: verify data mismatch", destlist[hostidx].name); free(verifydata); return 0; } } destlist[hostidx].status = DEST_REGISTERED; free(verifydata); return 1; } /** * For a given client, calculate the master key and do key expansion * to determine the symmetric cypher key and IV salt, and hash key */ void calculate_client_keys(int hostidx) { unsigned char *seed, *prf_buf; int explen, len, seedlen; explen = keylen + SALT_LEN + hmaclen; seedlen = sizeof(rand1) * 2; seed = safe_calloc(seedlen, 1); prf_buf = safe_calloc(MASTER_LEN + explen + hmaclen, 1); memcpy(seed, rand1, sizeof(rand1)); memcpy(seed + sizeof(rand1), destlist[hostidx].encinfo->rand2, sizeof(destlist[hostidx].encinfo->rand2)); PRF(hashtype, MASTER_LEN, destlist[hostidx].encinfo->premaster, destlist[hostidx].encinfo->premaster_len, "master secret", seed, seedlen, prf_buf, &len); memcpy(destlist[hostidx].encinfo->master,prf_buf, sizeof(destlist[hostidx].encinfo->master)); PRF(hashtype, explen, destlist[hostidx].encinfo->master, sizeof(destlist[hostidx].encinfo->master), "key expansion", seed, seedlen, prf_buf, &len); memcpy(destlist[hostidx].encinfo->hmackey, prf_buf, hmaclen); memcpy(destlist[hostidx].encinfo->key, prf_buf + hmaclen, keylen); memcpy(destlist[hostidx].encinfo->salt, prf_buf + hmaclen + keylen, SALT_LEN); free(seed); free(prf_buf); } /** * Processes encryption key information received in a REGISTER message */ int handle_register_keys(const struct register_h *reg, const unsigned char *keyinfo, struct finfo_t *finfo, int hostidx) { unsigned char premaster[PUBKEY_LEN]; unsigned int len; destlist[hostidx].encinfo = safe_calloc(1, sizeof(struct encinfo_t)); memcpy(destlist[hostidx].encinfo->rand2, reg->rand2, sizeof(destlist[hostidx].encinfo->rand2)); if (keyextype == KEYEX_RSA) { if (!RSA_decrypt(privkey.rsa, keyinfo, ntohs(reg->keyinfo_len), premaster, &len)) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "failed to decrypt premaster secret", destlist[hostidx].name); return 0; } if (len != MASTER_LEN) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "decrypted premaster secret wrong length", destlist[hostidx].name); return 0; } } else { if (!import_EC_key(&destlist[hostidx].encinfo->dhkey.ec, keyinfo, ntohs(reg->keyinfo_len), 1)) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "failed to import ECDH key", destlist[hostidx].name); return 0; } if (get_EC_curve(destlist[hostidx].encinfo->dhkey.ec) != ecdh_curve) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "invalid curve for ECDH", destlist[hostidx].name); return 0; } if (!get_ECDH_key(destlist[hostidx].encinfo->dhkey.ec, dhkey.ec, premaster, &len)) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "failed to calculate premaster secret", destlist[hostidx].name); return 0; } } memcpy(destlist[hostidx].encinfo->premaster, premaster, len); destlist[hostidx].encinfo->premaster_len = len; calculate_client_keys(hostidx); if (destlist[hostidx].encinfo->pubkey.key) { if (!verify_client_key(finfo, hostidx)) { return 0; } } return 1; } /** * Process an expected REGISTER with open group membership */ void handle_open_register(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, uint32_t src, int regconf) { const struct register_h *reg; const uint32_t *idlist; const unsigned char *enckey; int clientcnt, hostidx; struct timeval tv1, tv2; reg = (const struct register_h *)message; enckey = (const unsigned char *)reg + sizeof(struct register_h); gettimeofday(&tv2, NULL); if (destcount == MAXDEST) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %08X: " "max destinations exceeded", ntohl(src)); send_abort(finfo, "Max destinations exceeded", su, src, 0, 0); return; } if ((meslen < (reg->hlen * 4U)) || ((reg->hlen * 4U) < sizeof(struct register_h) + ntohs(reg->keyinfo_len))) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %08X: " "invalid message size", ntohl(src)); send_abort(finfo, "Invalid message size", su, src, 0, 0); return; } clientcnt = (meslen - (reg->hlen * 4)) / 4; hostidx = add_dest_by_addr(src, finfo, DEST_MUTE, -1, (clientcnt > 0) ? 0 : -1); if (keytype != KEY_NONE) { if (!handle_register_keys(reg, enckey, finfo, hostidx)) { return; } } if (regconf) { finfo->deststate[hostidx].conf_sent = 0; } tv1.tv_sec = ntohl(reg->tstamp_sec); tv1.tv_usec = ntohl(reg->tstamp_usec); destlist[hostidx].rtt = diff_usec(tv2, tv1) / 1000000.0; destlist[hostidx].rtt_measured = 1; destlist[hostidx].registered = 1; destlist[hostidx].status = regconf ? DEST_ACTIVE : (client_auth ? DEST_MUTE : DEST_REGISTERED); log1(finfo->group_id, finfo->file_id, "Received REGISTER from %s %s", (clientcnt > 0) ? "proxy" : "client", destlist[hostidx].name); if (clientcnt > 0) { idlist = (const uint32_t *)(message + (reg->hlen * 4)); add_proxy_dests(finfo, idlist, su, clientcnt, hostidx, 1, destlist[hostidx].rtt); } log3(finfo->group_id, finfo->file_id, "send time = %d.%06d", tv1.tv_sec, tv1.tv_usec); log3(finfo->group_id, finfo->file_id, "rx time = %d.%06d", tv2.tv_sec, tv2.tv_usec); log3(finfo->group_id,finfo->file_id, " rtt = %.6f", destlist[hostidx].rtt); } /** * Process an expected REGISTER with closed group membership, * or with open group membership if CLIENT_KEY was received first. */ void handle_register(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, int hostidx, int regconf, int open) { const struct register_h *reg; const uint32_t *idlist; const unsigned char *enckey; int clientcnt, dupmsg, isproxy; struct timeval tv1, tv2; reg = (const struct register_h *)message; enckey = (const unsigned char *)reg + sizeof(struct register_h); gettimeofday(&tv2, NULL); if ((meslen < (reg->hlen * 4U)) || ((reg->hlen * 4U) < sizeof(struct register_h) + ntohs(reg->keyinfo_len))) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "invalid message size", destlist[hostidx].name); send_abort(finfo, "Invalid message size", su, destlist[hostidx].id,0,0); return; } clientcnt = (meslen - (reg->hlen * 4)) / 4; if ((clientcnt > 0) && (destlist[hostidx].clientcnt == -1) && (!open)) { log1(finfo->group_id, finfo->file_id, "Rejecting REGISTER from %s: " "specified multiple clients but not a proxy", destlist[hostidx].name); send_abort(finfo, "specified multiple clients but not a proxy", su, destlist[hostidx].id, 0, 0); destlist[hostidx].status = DEST_ABORT; return; } if (finfo->file_id != 0) { log1(finfo->group_id, finfo->file_id, "Received REGISTER+ from %s", destlist[hostidx].name); return; } if (destlist[hostidx].status == DEST_MUTE) { if (keytype != KEY_NONE) { if (!handle_register_keys(reg, enckey, finfo, hostidx)) { return; } } destlist[hostidx].status = regconf ? DEST_ACTIVE : ((client_auth && (!destlist[hostidx].encinfo->pubkey.key)) ? DEST_MUTE : DEST_REGISTERED); } dupmsg = (destlist[hostidx].registered); tv1.tv_sec = ntohl(reg->tstamp_sec); tv1.tv_usec = ntohl(reg->tstamp_usec); destlist[hostidx].rtt = diff_usec(tv2, tv1) / 1000000.0; destlist[hostidx].rtt_measured = 1; destlist[hostidx].registered = 1; if (regconf) { finfo->deststate[hostidx].conf_sent = 0; } isproxy = (destlist[hostidx].clientcnt != -1); log1(finfo->group_id, finfo->file_id, "Received REGISTER%s from %s %s", (dupmsg && !isproxy) ? "+" : "", (isproxy) ? "proxy" : "client", destlist[hostidx].name); if (clientcnt > 0) { idlist = (const uint32_t *)(message + (reg->hlen * 4)); add_proxy_dests(finfo, idlist, su, clientcnt, hostidx, open, destlist[hostidx].rtt); } log3(finfo->group_id, finfo->file_id, "send time = %d.%06d", tv1.tv_sec, tv1.tv_usec); log3(finfo->group_id, finfo->file_id, "rx time = %d.%06d", tv2.tv_sec, tv2.tv_usec); log3(finfo->group_id,finfo->file_id, " rtt = %.6f", destlist[hostidx].rtt); } /** * Verifies a client's public key fingerprint */ int verify_client_fingerprint(const struct finfo_t *finfo, const unsigned char *keyblob, uint16_t bloblen, int hostidx) { unsigned char fingerprint[HMAC_LEN]; unsigned int fplen; if (destlist[hostidx].verified) { return 1; } if (keyblob[0] == KEYBLOB_RSA) { if (!import_RSA_key(&destlist[hostidx].encinfo->pubkey.rsa, keyblob, bloblen)) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY " "from %s: failed to import key", destlist[hostidx].name); return 0; } destlist[hostidx].encinfo->pubkeylen = RSA_keylen(destlist[hostidx].encinfo->pubkey.rsa); } else { if (!import_EC_key(&destlist[hostidx].encinfo->pubkey.ec, keyblob, bloblen, 0)) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY " "from %s: failed to import key", destlist[hostidx].name); return 0; } destlist[hostidx].encinfo->pubkeylen = ECDSA_siglen(destlist[hostidx].encinfo->pubkey.ec); } if (destlist[hostidx].has_fingerprint) { hash(HASH_SHA1, keyblob, bloblen, fingerprint, &fplen); if (memcmp(destlist[hostidx].keyfingerprint, fingerprint, fplen)) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY " "from %s: key fingerprint mismatch", destlist[hostidx].name); if (keyblob[0] == KEYBLOB_RSA) { free_RSA_key(destlist[hostidx].encinfo->pubkey.rsa); } else { free_EC_key(destlist[hostidx].encinfo->pubkey.ec); } destlist[hostidx].encinfo->pubkey.key = 0; destlist[hostidx].encinfo->pubkeylen = 0; return 0; } } destlist[hostidx].verified = 1; return 1; } /** * Process an expected CLIENT_KEY with open group membership */ void handle_open_clientkey(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, uint32_t src) { const struct client_key_h *clientkey; const unsigned char *keyblob, *verify; int hostidx; clientkey = (const struct client_key_h *)message; keyblob = (const unsigned char *)clientkey + sizeof(struct client_key_h); verify = keyblob + ntohs(clientkey->bloblen); if (destcount == MAXDEST) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY from %08X: " "max destinations exceeded", ntohl(src)); send_abort(finfo, "Max destinations exceeded", su, src, 0, 0); return; } if ((meslen < (clientkey->hlen * 4U)) || ((clientkey->hlen * 4U) < sizeof(struct client_key_h) + ntohs(clientkey->bloblen) + ntohs(clientkey->siglen))) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY from %08X: " "invalid message size", ntohl(src)); send_abort(finfo, "Invalid message size", su, src, 0, 0); return; } if ((((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) && (keyblob[0] != KEYBLOB_RSA)) || ((keyextype == KEYEX_ECDH_ECDSA) && (keyblob[0] != KEYBLOB_EC))) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY from %08X: " "invalid keyblob type", ntohl(src)); return; } hostidx = add_dest_by_addr(src, finfo, DEST_MUTE, -1, -1); if (!verify_client_fingerprint(finfo, keyblob, ntohs(clientkey->bloblen), hostidx)) { return; } memcpy(destlist[hostidx].encinfo->verifydata, verify, ntohs(clientkey->siglen)); destlist[hostidx].encinfo->verifylen = ntohs(clientkey->siglen); log2(finfo->group_id, finfo->file_id, "Received CLIENT_KEY from %s", destlist[hostidx].name); } /** * Process an expected CLIENT_KEY with closed group membership, * or with open group membersip if REGISTER was received first. */ void handle_clientkey(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, int hostidx) { const struct client_key_h *clientkey; const unsigned char *keyblob, *verify; clientkey = (const struct client_key_h *)message; keyblob = (const unsigned char *)clientkey + sizeof(struct client_key_h); verify = keyblob + ntohs(clientkey->bloblen); if ((meslen < (clientkey->hlen * 4U)) || ((clientkey->hlen * 4U) < sizeof(struct client_key_h) + ntohs(clientkey->bloblen) + ntohs(clientkey->siglen))) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY from %s: " "invalid message size", destlist[hostidx].name); send_abort(finfo, "Invalid message size", su, destlist[hostidx].id,0,0); return; } if ((((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) && (keyblob[0] != KEYBLOB_RSA)) || ((keyextype == KEYEX_ECDH_ECDSA) && (keyblob[0] != KEYBLOB_EC))) { log1(finfo->group_id, finfo->file_id, "Rejecting CLIENT_KEY from %s: " "invalid keyblob type", destlist[hostidx].name); return; } if (finfo->file_id != 0) { log2(finfo->group_id, finfo->file_id, "Received CLIENT_KEY+ from %s", destlist[hostidx].name); return; } if (!verify_client_fingerprint(finfo, keyblob, ntohs(clientkey->bloblen), hostidx)) { return; } memcpy(destlist[hostidx].encinfo->verifydata, verify, ntohs(clientkey->siglen)); destlist[hostidx].encinfo->verifylen = ntohs(clientkey->siglen); if (destlist[hostidx].registered) { if (!verify_client_key(finfo, hostidx)) { return; } } log2(finfo->group_id, finfo->file_id, "Received CLIENT_KEY from %s", destlist[hostidx].name); } /** * Process an expected KEYINFO_ACK and validate the verify_data field. */ void handle_keyinfo_ack(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, int hostidx) { const struct keyinfoack_h *keyinfoack; unsigned char *verifydata, *verify_hash, *verify_test; int verifylen, len, dupmsg; unsigned int hashlen; keyinfoack = (const struct keyinfoack_h *)message; if ((meslen < (keyinfoack->hlen * 4U)) || ((keyinfoack->hlen * 4U) < sizeof(struct keyinfoack_h))) { log1(finfo->group_id, finfo->file_id, "Rejecting KEYINFO_ACK from %s: " "invalid message size", destlist[hostidx].name); send_abort(finfo, "Invalid message size", su, destlist[hostidx].id,0,0); return; } if (keytype == KEY_NONE) { log1(finfo->group_id, finfo->file_id, "Rejecting KEYINFO_ACK from %s: " "encryption not enabled", destlist[hostidx].name); send_abort(finfo, "Encryption not enabled", su, destlist[hostidx].id, 0, 0); return; } if (!(verifydata = build_verify_data(finfo, hostidx, &verifylen))) { log1(finfo->group_id, finfo->file_id, "Rejecting KEYINFO_ACK from %s: " "error exporting client public key", destlist[hostidx].name); return; } verify_hash = safe_calloc(hmaclen, 1); verify_test = safe_calloc(VERIFY_LEN + hmaclen, 1); hash(hashtype, verifydata, verifylen, verify_hash, &hashlen); PRF(hashtype, VERIFY_LEN, groupmaster, sizeof(groupmaster), "client finished", verify_hash, hashlen, verify_test, &len); if (memcmp(keyinfoack->verify_data, verify_test, VERIFY_LEN)) { log1(finfo->group_id, finfo->file_id, "Rejecting KEYINFO_ACK from %s: " "verify data mismatch", destlist[hostidx].name); free(verifydata); free(verify_hash); free(verify_test); return; } free(verifydata); free(verify_hash); free(verify_test); dupmsg = (destlist[hostidx].status == DEST_ACTIVE); log2(finfo->group_id, finfo->file_id, "Received KEYINFO_ACK%s from %s", dupmsg ? "+" : "", destlist[hostidx].name); destlist[hostidx].status = DEST_ACTIVE; } /** * Process an expected FILEINFO_ACK. */ void handle_fileinfo_ack(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx) { const struct fileinfoack_h *fileinfoack; struct timeval tv1, tv2; const uint32_t *addr; int clientcnt, dupmsg, isproxy, clientidx, i; fileinfoack = (const struct fileinfoack_h *)message; gettimeofday(&tv2, NULL); if ((meslen < (fileinfoack->hlen * 4U)) || ((fileinfoack->hlen * 4U) < sizeof(struct fileinfoack_h))) { log1(finfo->group_id, finfo->file_id, "Rejecting FILEINFO_ACK from %s: " "invalid message size", destlist[hostidx].name); return; } clientcnt = (meslen - (fileinfoack->hlen * 4)) / 4; if ((clientcnt > 0) && (destlist[hostidx].clientcnt == -1)) { log1(finfo->group_id, finfo->file_id, "Rejecting FILEINFO_ACK from %s: " "specified multiple clients but not a proxy", destlist[hostidx].name); return; } if (ntohs(fileinfoack->file_id) != finfo->file_id) { log1(finfo->group_id, finfo->file_id, "Rejecting FILEINFO_ACK from %s: " "invalid file ID %04X, expected %04X ", destlist[hostidx].name, ntohs(fileinfoack->file_id), finfo->file_id ); return; } finfo->partial = finfo->partial && ((fileinfoack->flags & FLAG_PARTIAL) != 0); tv1.tv_sec = ntohl(fileinfoack->tstamp_sec); tv1.tv_usec = ntohl(fileinfoack->tstamp_usec); destlist[hostidx].rtt = diff_usec(tv2, tv1) / 1000000.0; destlist[hostidx].rtt_measured = 1; destlist[hostidx].rtt_sent = 0; isproxy = (destlist[hostidx].clientcnt != -1); dupmsg = (destlist[hostidx].status == DEST_ACTIVE); destlist[hostidx].status = DEST_ACTIVE; log2(finfo->group_id, finfo->file_id, "Received FILEINFO_ACK%s from %s %s", (dupmsg && !isproxy) ? "+" : "", (isproxy) ? "proxy" : "client", destlist[hostidx].name); if (clientcnt > 0) { addr = (const uint32_t *)(message + (fileinfoack->hlen * 4)); for (i = 0; i < clientcnt; i++) { dupmsg = 0; clientidx = find_client(addr[i]); if (clientidx == -1) { log2(finfo->group_id, finfo->file_id, "Host %08X not in host list", ntohl(addr[i])); continue; } else { dupmsg = (destlist[clientidx].status == DEST_ACTIVE); destlist[clientidx].status = DEST_ACTIVE; destlist[clientidx].rtt = destlist[hostidx].rtt; } log2(finfo->group_id, finfo->file_id, " For client%s %s", dupmsg ? "+" : "", destlist[clientidx].name); } } log3(finfo->group_id, finfo->file_id, "send time = %d.%06d", tv1.tv_sec, tv1.tv_usec); log3(finfo->group_id, finfo->file_id, "rx time = %d.%06d", tv2.tv_sec, tv2.tv_usec); log3(finfo->group_id,finfo->file_id, " rtt = %.6f", destlist[hostidx].rtt); return; } uftp-4.1.5/client_fileinfo.h0000664000076400007640000000315512250244021015003 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_FILEINFO_H #define _CLIENT_FILEINFO_H void handle_fileinfo(struct group_list_t *group, const unsigned char *message, unsigned meslen, struct timeval rxtime); void send_fileinfo_ack(struct group_list_t *group, int restart); #endif // _CLIENT_FILEINFO_H uftp-4.1.5/server_config.c0000644000076400007640000010137212250244021014476 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #ifdef WINDOWS #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #endif #include "server.h" #include "server_config.h" /** * Global command line values and sockets */ SOCKET sock; union sockaddr_u listen_dest, receive_dest; int max_rate, rate, rcvbuf, packet_wait, txweight, max_nak_pct; int client_auth, quit_on_error, dscp, follow_links, max_nak_cnt; int save_fail, restart_groupid, restart_groupinst; int sync_mode, sync_preview, dest_is_dir, cc_type, user_abort; unsigned int ttl; char port[PORTNAME_LEN], srcport[PORTNAME_LEN]; char pub_multi[INET6_ADDRSTRLEN], priv_multi[INET6_ADDRSTRLEN]; char keyfile[MAXPATHNAME], cc_config[MAXPATHNAME]; char filelist[MAXFILES][MAXPATHNAME], exclude[MAXEXCLUDE][MAXPATHNAME]; char basedir[MAXDIR][MAXDIRNAME], destfname[MAXPATHNAME]; char statusfilename[MAXPATHNAME]; FILE *status_file; struct iflist ifl[MAX_INTERFACES]; int keytype, hashtype, sigtype, keyextype, newkeylen, sys_keys; int blocksize, datapacketsize; int ifl_len, destcount, filecount, excludecount, basedircount, cc_count; struct cc_config_t cc_list[MAXCC]; struct iflist out_if; struct destinfo_t destlist[MAXDEST]; int robust; double grtt, min_grtt, max_grtt; uint16_t send_seq; uint32_t server_id; /** * Encryption variables */ union key_t privkey, dhkey; unsigned char rand1[RAND_LEN], groupmaster[MASTER_LEN]; uint8_t groupsalt[MAXIV], groupkey[MAXKEY], grouphmackey[HMAC_LEN]; uint64_t ivctr; int ivlen, keylen, hmaclen, privkeylen; uint8_t ecdh_curve, ecdsa_curve; extern char *optarg; extern int optind; /** * Add a destination or proxy to the list as specified by -H or -j */ void add_dest_by_name(const char *destname, const char *fingerprint, int proxy) { struct addrinfo ai_hints, *ai_rval; uint32_t uid; int rval; if (destcount == MAXDEST) { fprintf(stderr,"Exceeded maximum destination count\n"); exit(1); } // Check if the client is specified by an IPv4 name/address ai_hints.ai_family = AF_INET; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(destname, NULL, &ai_hints, &ai_rval)) != 0) { uid = strtoul(destname, NULL, 16); if ((uid == 0xffffffff) || (uid == 0)) { fprintf(stderr, "Invalid UID %s\n", destname); exit(1); } destlist[destcount].id = htonl(uid); } else { destlist[destcount].id = ((struct sockaddr_in *)ai_rval->ai_addr)->sin_addr.s_addr; freeaddrinfo(ai_rval); } snprintf(destlist[destcount].name, sizeof(destlist[destcount].name), "%s", destname); destlist[destcount].proxyidx = -1; destlist[destcount].clientcnt = proxy ? 0 : -1; destlist[destcount].has_fingerprint = parse_fingerprint(destlist[destcount].keyfingerprint, fingerprint); destcount++; } /** * Set defaults for all command line arguments */ void set_defaults(void) { memset(destlist, 0, sizeof(destlist)); memset(filelist, 0, sizeof(filelist)); memset(exclude, 0, sizeof(exclude)); memset(destfname, 0, sizeof(destfname)); memset(cc_config, 0, sizeof(cc_config)); destcount = 0; strncpy(port, DEF_PORT, sizeof(port)-1); port[sizeof(port)-1] = '\x0'; strncpy(srcport, DEF_SRCPORT, sizeof(srcport)-1); srcport[sizeof(srcport)-1] = '\x0'; rate = DEF_RATE; max_rate = 0; memset(&out_if, 0, sizeof(out_if)); ttl = DEF_TTL; dscp = DEF_DSCP; blocksize = DEF_BLOCKSIZE; log_level = DEF_LOG_LEVEL; rcvbuf = 0; memset(keyfile, 0, sizeof(keyfile)); client_auth = 0; quit_on_error = 0; save_fail = 0; restart_groupid = 0; restart_groupinst = 0; keytype = DEF_KEYTYPE; hashtype = DEF_HASHTYPE; sigtype = DEF_SIGTYPE; keyextype = DEF_KEYEXTYPE; ecdh_curve = DEF_CURVE; strncpy(pub_multi, DEF_PUB_MULTI, sizeof(pub_multi)-1); pub_multi[sizeof(pub_multi)-1] = '\x0'; strncpy(priv_multi, DEF_PRIV_MULTI, sizeof(priv_multi)-1); priv_multi[sizeof(priv_multi)-1] = '\x0'; strncpy(logfile, "", sizeof(logfile)-1); logfile[sizeof(logfile)-1] = '\x0'; strncpy(statusfilename, "", sizeof(statusfilename)-1); statusfilename[sizeof(statusfilename)-1] = '\x0'; status_file = NULL; filecount = 0; excludecount = 0; basedircount = 0; newkeylen = 0; ecdh_curve = 0; ecdsa_curve = 0; follow_links = 0; showtime = 0; sys_keys = 0; sync_mode = 0; sync_preview = 0; cc_count = 0; dest_is_dir = 0; grtt = DEF_GRTT; min_grtt = DEF_MIN_GRTT; max_grtt = DEF_MAX_GRTT; robust = DEF_ROBUST; send_seq = 0; user_abort = 0; txweight = DEF_TXWEIGHT; max_nak_pct = DEF_MAX_NAK_PCT; max_nak_cnt = DEF_MAX_NAK_CNT; max_log_size = 0; max_log_count = DEF_MAX_LOG_COUNT; } /** * Reads in the contents of the restart file. * Contains a server_restart_t header, followed by * one or more server_restart_host_t entries. */ void read_restart_file(const char *restart_name) { struct server_restart_t header; struct server_restart_host_t host; int fd, i, rval; if ((fd = open(restart_name, OPENREAD)) == -1) { syserror(0, 0, "Failed to open restart file"); exit(1); } if (file_read(fd, &header, sizeof(header), 0) == -1) { log0(0, 0, "Failed to read header from restart file"); close(fd); exit(1); } restart_groupid = header.group_id; restart_groupinst = header.group_inst; if (restart_groupinst == 0xff) { log0(0, 0, "Maximum number of restarts reached"); close(fd); exit(1); } if ((header.filecount > MAXFILES) || (header.filecount <= 0)) { log0(0, 0, "Too many files listed in restart file"); close(fd); exit(1); } for (i = 0; i < header.filecount; i++) { if (file_read(fd, filelist[i], sizeof(filelist[i]), 0) == -1) { log0(0, 0, "Failed to read filename from restart file"); close(fd); exit(1); } } filecount = header.filecount; while ((rval = file_read(fd, &host, sizeof(host), 1)) != 0) { if (rval == -1) { log0(0, 0, "Failed to read host from restart file"); close(fd); exit(1); } memcpy(destlist[destcount].name, host.name, sizeof(host.name)); destlist[destcount].id = host.id; destlist[destcount].proxyidx = -1; destlist[destcount].clientcnt = host.is_proxy ? 0 : -1; destlist[destcount].has_fingerprint = host.has_fingerprint; if (host.has_fingerprint) { memcpy(destlist[destcount].keyfingerprint, host.keyfingerprint, sizeof(destlist[destcount].keyfingerprint)); } destcount++; } close(fd); } /** * Reads in the congestion control config file. * Each line contains a percentage (0-100) followed by a scaling factor * or the string "max" followed by the maximum transmission rate in Kbps * Sample: * max;10000 * 0;1.3 * 5;1.1 * 10;0.9 * 25;0.7 * 50;0.4 * * Returns 1 on success, 0 on fail. */ int read_cc_config(const char *filename) { FILE *cc_file; char line[100], *p; int last_percentage, percentage; double scaling_factor; struct cc_config_t tmp_cc_list[MAXCC]; int tmp_cc_count, tmp_max_rate; if ((cc_file = fopen(filename, "rt")) == NULL) { log0(0, 0, "Couldn't open congestion control file %s: %s\n", cc_config, strerror(errno)); return 0; } tmp_cc_count = 0; tmp_max_rate = 0; last_percentage = 0; while (fgets(line, sizeof(line), cc_file)) { while ((strlen(line) > 0) && ((line[strlen(line)-1] == '\r') || (line[strlen(line)-1] == '\n'))) { line[strlen(line)-1] = '\x0'; } if (strlen(line) == 0) continue; p = strtok(line, ";"); if (p == NULL) { log0(0, 0, "Error reading congestion control entry\n"); fclose(cc_file); return 0; } if (!strcmp(p, "max")) { if (tmp_max_rate) { log0(0, 0, "Cannot have multiple entries for max rate\n"); fclose(cc_file); return 0; } p = strtok(NULL, ";"); if (p == NULL) { log0(0, 0, "Error reading congestion control entry\n"); fclose(cc_file); return 0; } // Change from Kbps to B/S tmp_max_rate = strtol(p, NULL, 10) * 1024 / 8; if (tmp_max_rate <= 0) { log0(0, 0, "Invalid max rate in congestion control\n"); fclose(cc_file); return 0; } continue; } percentage = strtol(p, NULL, 10); if ((percentage < 0) || percentage > 100) { log0(0, 0, "Invalid percentage in congestion control entry\n"); fclose(cc_file); return 0; } if (percentage < last_percentage) { log0(0, 0, "Congestion control entries must be in ascending " "order by percentage\n"); fclose(cc_file); return 0; } p = strtok(NULL, ";"); if (p == NULL) { log0(0, 0, "Error reading congestion control entry\n"); fclose(cc_file); return 0; } errno = 0; scaling_factor = strtod(p, NULL); if (errno) { log0(0, 0, "Error reading congestion control entry: %s\n", strerror(errno)); fclose(cc_file); return 0; } if (scaling_factor <= 0) { log0(0, 0, "Invalid scaling factor in congestion control entry\n"); fclose(cc_file); return 0; } last_percentage = percentage; tmp_cc_list[tmp_cc_count].percentage = percentage; tmp_cc_list[tmp_cc_count].scaling_factor = scaling_factor; tmp_cc_count++; } fclose(cc_file); // Make sure we have a top end entry, make scaling same as last specified tmp_cc_list[tmp_cc_count].percentage = 100; tmp_cc_list[tmp_cc_count].scaling_factor = tmp_cc_list[tmp_cc_count - 1].scaling_factor; tmp_cc_count++; // Config successfully read in, copy to real list for (cc_count = 0; cc_count < tmp_cc_count; cc_count++) { cc_list[cc_count] = tmp_cc_list[cc_count]; } if (tmp_max_rate) { max_rate = tmp_max_rate; } else if (!max_rate) { max_rate = rate; } return 1; } /** * Gets the symmetric cypher constant for the given cypher name * Returns -1 if the name is invalid */ static int get_keytype(const char *name) { if (!strcmp(optarg, "none")) { return KEY_NONE; } else if (!strcmp(optarg, "des")) { return KEY_DES; } else if (!strcmp(optarg, "3des")) { return KEY_DES_EDE3; } else if (!strcmp(optarg, "aes128-cbc")) { return KEY_AES128_CBC; } else if (!strcmp(optarg, "aes256-cbc")) { return KEY_AES256_CBC; } else if (!strcmp(optarg, "aes128-gcm")) { return KEY_AES128_GCM; } else if (!strcmp(optarg, "aes256-gcm")) { return KEY_AES256_GCM; } else if (!strcmp(optarg, "aes128-ccm")) { return KEY_AES128_CCM; } else if (!strcmp(optarg, "aes256-ccm")) { return KEY_AES256_CCM; } else { return -1; } } /** * Gets the hash constant for the given hash name * Returns -1 if the name is invalid */ static int get_hashtype(const char *name) { if (!strcmp(optarg, "sha1")) { return HASH_SHA1; } else if (!strcmp(optarg, "sha256")) { return HASH_SHA256; } else if (!strcmp(optarg, "sha384")) { return HASH_SHA384; } else if (!strcmp(optarg, "sha512")) { return HASH_SHA512; } else { return -1; } } /** * Set argument defaults, read and validate command line options */ void process_args(int argc, char *argv[]) { int c, i, listidx, read_restart, rval; long tmpval; char line[1000], *dest, *destname, filename[MAXPATHNAME], *fingerprint, *p; char keylenstr[50]; struct addrinfo ai_hints, *ai_rval; FILE *destfile, *excludefile, *listfile; const char opts[] = "x:R:L:B:g:n:m:Y:h:w:e:ck:K:lTb:t:Q:" "zZI:p:u:j:qfyH:F:X:M:P:C:D:oE:S:r:s:i:W:N:"; set_defaults(); memset(keylenstr, 0, sizeof(keylenstr)); read_restart = 0; // read lettered arguments while ((c = getopt(argc, argv, opts)) != EOF) { switch (c) { case 'x': log_level = atoi(optarg); if (log_level < 0) { fprintf(stderr,"Invalid log level\n"); exit(1); } break; case 'R': // Expecting rate as Kbps, translate to B/s rate = atoi(optarg); if ((rate <= 0) && (rate != -1)) { fprintf(stderr,"Invalid rate\n"); exit(1); } if (rate != -1) { rate = rate * 1024 / 8; } if ((rate == -1) && (cc_count > 0)) { fprintf(stderr,"Can't specify -R -1 with -C\n"); exit(1); } break; case 'L': strncpy(logfile, optarg, sizeof(logfile)-1); logfile[sizeof(logfile)-1] = '\x0'; break; case 'B': rcvbuf = atoi(optarg); if ((rcvbuf < 65536) || (rcvbuf > 104857600)) { fprintf(stderr, "Invalid receive buffer size\n"); exit(1); } break; case 'g': max_log_size = atoi(optarg); if ((max_log_size < 1) || (max_log_size > 1024)) { fprintf(stderr, "Invalid max log size\n"); exit(1); } max_log_size *= 1000000; break; case 'n': max_log_count = atoi(optarg); if ((max_log_count < 1) || (max_log_count > 1000)) { fprintf(stderr, "Invalid max log count\n"); exit(1); } break; case 'm': max_nak_cnt = atoi(optarg); if ((max_nak_cnt < 1) || (max_nak_cnt > 1000)) { fprintf(stderr, "Invalid max nak count\n"); exit(1); } break; case 'Y': if ((keytype = get_keytype(optarg)) == -1) { fprintf(stderr, "Invalid keytype\n"); exit(1); } if (keytype != KEY_NONE && !cipher_supported(keytype)) { fprintf(stderr, "Keytype not supported\n"); exit(1); } break; case 'h': if ((hashtype = get_hashtype(optarg)) == -1) { fprintf(stderr, "Invalid hashtype\n"); exit(1); } if (!hash_supported(hashtype)) { fprintf(stderr, "Hashtype not supported\n"); exit(1); } break; case 'w': if (!strcmp(optarg, "hmac")) { sigtype = SIG_HMAC; } else if (!strcmp(optarg, "keyex")) { sigtype = SIG_KEYEX; } else { fprintf(stderr, "Invalid sigtype\n"); exit(1); } break; case 'e': p = strtok(optarg, ":"); if (!p) { fprintf(stderr, "Error reading keyextype\n"); exit(1); } if (!strcmp(p, "rsa")) { keyextype = KEYEX_RSA; } else if (!strcmp(p, "ecdh_rsa")) { keyextype = KEYEX_ECDH_RSA; } else if (!strcmp(p, "ecdh_ecdsa")) { keyextype = KEYEX_ECDH_ECDSA; } else { fprintf(stderr, "Invalid keyextype\n"); exit(1); } if ((keyextype == KEYEX_ECDH_RSA) || (keyextype == KEYEX_ECDH_ECDSA)) { p = strtok(NULL, ":"); if (p) { ecdh_curve = get_curve(p); if (ecdh_curve == 0) { fprintf(stderr, "Invalid curve\n"); exit(1); } } else { ecdh_curve = DEF_CURVE; } } break; case 'c': client_auth = 1; break; case 'k': strncpy(keyfile, optarg, sizeof(keyfile)-1); keyfile[sizeof(keyfile)-1] = '\x0'; break; case 'K': strncpy(keylenstr, optarg, sizeof(keylenstr)-1); keylenstr[sizeof(keylenstr)-1] = '\x0'; break; case 'l': follow_links = 1; break; case 'T': showtime = 1; break; case 'b': blocksize = atoi(optarg); if ((blocksize < 512) || (blocksize > (MAXMTU - 200))) { fprintf(stderr, "Invalid blocksize\n"); exit(1); } break; case 't': tmpval = atoi(optarg); if ((tmpval <= 0) || (tmpval > 255)) { fprintf(stderr, "Invalid ttl\n"); exit(1); } ttl = (char)tmpval; break; case 'Q': tmpval = strtol(optarg, NULL, 0); if ((tmpval < 0) || (tmpval > 63)) { fprintf(stderr, "Invalid dscp\n"); exit(1); } dscp = (tmpval & 0xFF) << 2; break; case 'I': if ((listidx = getifbyname(optarg, ifl, ifl_len)) != -1) { out_if = ifl[listidx]; break; } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(optarg, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Invalid name/address %s: %s\n", optarg, gai_strerror(rval)); exit(1); } // Just use the first addrinfo entry if ((listidx = getifbyaddr((union sockaddr_u *)ai_rval->ai_addr, ifl, ifl_len)) == -1) { fprintf(stderr, "Interface %s not found", optarg); exit(1); } out_if = ifl[listidx]; freeaddrinfo(ai_rval); break; case 'z': sync_mode = 1; break; case 'Z': sync_preview = 1; sync_mode = 1; break; case 'p': strncpy(port, optarg, sizeof(port)-1); port[sizeof(port)-1] = '\x0'; break; case 'u': strncpy(srcport, optarg, sizeof(srcport)-1); srcport[sizeof(srcport)-1] = '\x0'; break; case 'j': if (read_restart) { fprintf(stderr,"Can't specify both -j and -F\n"); exit(1); } if ((destfile = fopen(optarg, "rt")) == NULL) { fprintf(stderr,"Couldn't open proxy list %s: %s\n", optarg, strerror(errno)); exit(1); } while (fgets(line, sizeof(line), destfile)) { while ((strlen(line) > 0) && ((line[strlen(line)-1] == '\r') || (line[strlen(line)-1] == '\n'))) { line[strlen(line)-1] = '\x0'; } destname = strtok(line, "|"); if (!destname) continue; if (destname[0] == '#') continue; if (strlen(destname) >= DESTNAME_LEN) { fprintf(stderr, "Proxylist: name too long\n"); exit(1); } fingerprint = strtok(NULL, " \t"); add_dest_by_name(destname, fingerprint, 1); } if (!feof(destfile) && ferror(destfile)) { perror("Failed to read from proxylist file"); exit(1); } fclose(destfile); break; case 'q': quit_on_error = 1; break; case 'f': save_fail = 1; break; case 'y': sys_keys = 1; break; case 'H': if (read_restart) { fprintf(stderr,"Can't specify both -H and -F\n"); exit(1); } if (optarg[0] == '@') { dest = &optarg[1]; if ((destfile = fopen(dest, "rt")) == NULL) { fprintf(stderr,"Couldn't open destination list %s: %s\n", dest, strerror(errno)); exit(1); } while (fgets(line, sizeof(line), destfile)) { while ((strlen(line) > 0) && ((line[strlen(line)-1] == '\r') || (line[strlen(line)-1] == '\n'))) { line[strlen(line)-1] = '\x0'; } destname = strtok(line, "|"); if (!destname) continue; if (destname[0] == '#') continue; if (strlen(destname) >= DESTNAME_LEN) { fprintf(stderr, "Hostlist: name too long\n"); exit(1); } fingerprint = strtok(NULL, " \t"); add_dest_by_name(destname, fingerprint, 0); } if (!feof(destfile) && ferror(destfile)) { perror("Failed to read from hostlist file"); exit(1); } fclose(destfile); } else { dest = strtok(optarg, ","); while (dest != NULL) { add_dest_by_name(dest, NULL, 0); dest = strtok(NULL, ","); } } break; case 'F': if (destcount != 0) { fprintf(stderr,"Can't specify both -H and -F\n"); exit(1); } read_restart = 1; save_fail = 1; read_restart_file(optarg); break; case 'X': if ((excludefile = fopen(optarg, "rt")) == NULL) { fprintf(stderr,"Couldn't open exclude list %s: %s\n", optarg, strerror(errno)); exit(1); } while (fgets(filename, sizeof(filename), excludefile)) { while ((strlen(filename) > 0) && ((filename[strlen(filename)-1] == '\r') || (filename[strlen(filename)-1] == '\n'))) { filename[strlen(filename)-1] = '\x0'; } if (strlen(filename) == 0) continue; if (excludecount == MAXEXCLUDE) { fprintf(stderr,"Exceeded maximum exclude file count\n"); exit(1); } strncpy(exclude[excludecount], filename, sizeof(exclude[0])); exclude[excludecount][sizeof(exclude[0])-1] = '\x0'; excludecount++; } if (!feof(excludefile) && ferror(excludefile)) { perror("Failed to read from exclude file"); exit(1); } fclose(excludefile); break; case 'M': strncpy(pub_multi, optarg, sizeof(pub_multi)-1); pub_multi[sizeof(pub_multi)-1] = '\x0'; break; case 'P': strncpy(priv_multi, optarg, sizeof(priv_multi)-1); priv_multi[sizeof(priv_multi)-1] = '\x0'; break; case 'C': p = strtok(optarg, ":"); if (!p) { fprintf(stderr, "Error reading cc_type\n"); exit(1); } if (!strcmp(p, "none")) { cc_type = CC_NONE; } else if (!strcmp(p, "uftp3")) { if (rate == -1) { fprintf(stderr,"Can't specify -C uftp3 with -R -1\n"); exit(1); } cc_type = CC_UFTP3; p = strtok(NULL, ":"); if (!p) { fprintf(stderr, "Error reading CC config file\n"); exit(1); } strncpy(cc_config, p, sizeof(cc_config)-1); cc_config[sizeof(cc_config)-1] = '\x0'; if (!read_cc_config(cc_config)) { fprintf(stderr,"Error loading congestion control config\n"); exit(1); } } else if (!strcmp(p, "tfmcc")) { cc_type = CC_TFMCC; } else { // PGMCC not currently supported fprintf(stderr, "Invalid congestion control type\n"); exit(1); } break; case 'D': strncpy(destfname, optarg, sizeof(destfname)-1); destfname[sizeof(destfname)-1] = '\x0'; while (destfname[strlen(destfname)-1] == PATH_SEP) { destfname[strlen(destfname)-1] = '\x0'; } break; case 'o': dest_is_dir = 1; break; case 'E': p = strtok(optarg, ","); while (p != NULL) { strncpy(basedir[basedircount], p, sizeof(basedir[basedircount])-1); basedir[basedircount][sizeof(basedir[basedircount])-1] = '\x0'; basedircount++; p = strtok(NULL, ","); } break; case 'S': strncpy(statusfilename, optarg, sizeof(statusfilename)-1); statusfilename[sizeof(statusfilename)-1] = '\x0'; break; case 'r': p = strtok(optarg, ":"); if (!p) { fprintf(stderr, "Error reading cc_type\n"); exit(1); } errno = 0; grtt = atof(p); if (errno) { perror("Invalid grtt"); exit(1); } else if ((grtt < CLIENT_RTT_MIN) || (grtt > 1000)) { fprintf(stderr, "Invalid grtt\n"); exit(1); } p = strtok(NULL, ":"); if (p) { errno = 0; min_grtt = atof(p); if (errno) { perror("Invalid min_grtt"); exit(1); } else if ((min_grtt < CLIENT_RTT_MIN) || (min_grtt > 1000)) { fprintf(stderr, "Invalid min_grtt\n"); exit(1); } p = strtok(NULL, ":"); if (!p) { fprintf(stderr, "Missing max_grtt\n"); exit(1); } errno = 0; max_grtt = atof(p); if (errno) { perror("Invalid max_grtt"); exit(1); } else if ((max_grtt < CLIENT_RTT_MIN) || (max_grtt > 1000)) { fprintf(stderr, "Invalid max_grtt\n"); exit(1); } if (min_grtt > max_grtt) { fprintf(stderr, "Invalid min_grtt/max_grtt\n"); exit(1); } else if ((grtt > max_grtt) || (grtt < min_grtt)) { fprintf(stderr, "Invalid grtt\n"); exit(1); } } break; case 's': robust = atoi(optarg); if ((robust < 10) || (robust > 50)) { fprintf(stderr,"Invalid robustness factor\n"); exit(1); } break; case 'i': if (filecount != 0) { fprintf(stderr,"Can't specify both -i and -F\n"); exit(1); } if (strcmp(optarg, "-") == 0) { listfile = stdin; } else if ((listfile = fopen(optarg, "rt")) == NULL) { fprintf(stderr,"Couldn't open file list %s: %s\n", optarg, strerror(errno)); exit(1); } while (fgets(filename, sizeof(filename), listfile)) { if (filecount == MAXFILES) { fprintf(stderr, "Exceeded maximum file count\n"); exit(1); } while ((strlen(filename) > 0) && ((filename[strlen(filename)-1] == '\r') || (filename[strlen(filename)-1] == '\n'))) { filename[strlen(filename)-1] = '\x0'; } if (strlen(filename) == 0) continue; strncpy(filelist[filecount], filename, sizeof(filelist[0])-1); filelist[filecount][sizeof(filelist[0])-1] = '\x0'; filecount++; } if (!feof(listfile) && ferror(listfile)) { perror("Failed to read from file list"); exit(1); } fclose(listfile); break; case 'W': txweight = atoi(optarg); if ((txweight < 110) || (txweight > 10000)) { fprintf(stderr, "Invalid txweight\n"); exit(1); } break; case 'N': max_nak_pct = atoi(optarg); if ((max_nak_pct < 0) || (max_nak_pct > 100)) { fprintf(stderr, "Invalid max_nak_pct\n"); exit(1); } break; case '?': fprintf(stderr, USAGE); exit(1); } } argc -= optind; argv += optind; if ((argc == 0) && (filecount == 0)) { fprintf(stderr, USAGE); exit(1); } if (save_fail && sync_mode) { fprintf(stderr, "Error: Cannot use restart mode " "and sync mode together\n"); exit(1); } if (keytype == KEY_NONE) { hashtype = HASH_NONE; sigtype = SIG_NONE; keyextype = KEYEX_NONE; } if (is_auth_enc(keytype)) { sigtype = SIG_AUTHENC; } if (strcmp(keylenstr, "")) { if (keyextype == KEYEX_ECDH_ECDSA) { ecdsa_curve = get_curve(keylenstr); if (ecdsa_curve == 0) { fprintf(stderr, "Invalid curve\n"); exit(1); } } else if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { newkeylen = atoi(keylenstr); if ((newkeylen < 512) || (newkeylen > 2048)) { fprintf(stderr, "Invalid new key length\n"); exit(1); } } } if (!max_rate) { max_rate = rate; } if (filecount != 0) { if (argc > 0) { fprintf(stderr, "Warning: ignoring paths " "specified on command line\n"); } return; } // Read list of files. for (i = 0; i < argc; i++) { if (filecount == MAXFILES) { fprintf(stderr, "Exceeded maximum file count\n"); exit(1); } strncpy(filelist[filecount], argv[i], sizeof(filelist[0])-1); filelist[filecount][sizeof(filelist[0])-1] = '\x0'; filecount++; } } uftp-4.1.5/client_announce.c0000644000076400007640000011614712250244021015015 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #ifdef WINDOWS #include #include #include #include "win_func.h" #else // if WINDOWS #include #include #include #endif #include "client.h" #include "client_common.h" #include "client_announce.h" /** * Finds next open slot in the global group list. * Returns a pointer to the open slot, or NULL if none found. */ struct group_list_t *find_open_slot(void) { int i; for (i = 0; i < MAXLIST; i++) { if (group_list[i].group_id == 0) { memset(&group_list[i], 0, sizeof(group_list[i])); return &group_list[i]; } } return NULL; } /** * Returns the verify_data string used in certain messages. This value * is then run through the PRF with the result going into the message. */ uint8_t *build_verify_data(struct group_list_t *group, int *verifylen) { uint8_t *verifydata; uint32_t group_id; int iplen; iplen = (group->multi.ss.ss_family == AF_INET6) ? sizeof(struct in6_addr) : sizeof(struct in_addr); *verifylen = 0; if (group->phase == PHASE_REGISTERED) { verifydata = safe_calloc(sizeof(group->group_id) + iplen + sizeof(group->rand1) + sizeof(group->rand2) + sizeof(group->premaster), 1); } else { verifydata = safe_calloc(sizeof(group->group_id) + iplen + sizeof(group->rand1) + sizeof(group->rand2) + sizeof(group->premaster) + PUBKEY_LEN + sizeof(group->groupmaster), 1); } group_id = htonl(group->group_id); memcpy(verifydata, &group_id, sizeof(group_id)); *verifylen += sizeof(group_id); if (group->multi.ss.ss_family == AF_INET6) { memcpy(verifydata + *verifylen, &group->multi.sin6.sin6_addr.s6_addr, iplen); } else { memcpy(verifydata + *verifylen, &group->multi.sin.sin_addr.s_addr, iplen); } *verifylen += iplen; memcpy(verifydata + *verifylen, group->rand1, sizeof(group->rand1)); *verifylen += sizeof(group->rand1); memcpy(verifydata + *verifylen, group->rand2, sizeof(group->rand2)); *verifylen += sizeof(group->rand2); memcpy(verifydata + *verifylen, group->premaster, group->premaster_len); *verifylen += group->premaster_len; if (group->phase != PHASE_REGISTERED) { if (group->client_auth) { uint16_t bloblen; uint8_t *keyblob = verifydata + *verifylen; if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (!export_RSA_key(group->client_privkey.rsa, keyblob, &bloblen)) { free(verifydata); return NULL; } } else { if (!export_EC_key(group->client_privkey.ec, keyblob, &bloblen)) { free(verifydata); return NULL; } } *verifylen += bloblen; } memcpy(verifydata + *verifylen, group->groupmaster, sizeof(group->groupmaster)); *verifylen += sizeof(group->groupmaster); } return verifydata; } /** * 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) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "Error signing verify data"); send_abort(group, "Error signing verify data"); goto end; } } else { if (!export_EC_key(group->client_privkey.ec, keyblob, &bloblen)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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) { sockerror(group->group_id, group->file_id, "Error sending CLIENT_KEY"); } else { log2(group->group_id, group->file_id, "CLIENT_KEY sent"); } end: free(verifydata); free(buf); } /** * 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) { log1(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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) { sockerror(group->group_id, group->file_id, "Error sending REGISTER"); } else { log2(group->group_id, group->file_id, "REGISTER sent"); } log3(group->group_id, 0, "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 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) { log0(group->group_id, group->file_id, "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)) { log0(0, 0, "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) { sockerror(group->group_id, group->file_id, "Error sending KEYINFO_ACK"); } else { log2(group->group_id, group->file_id, "KEYINFO_ACK sent"); } free(encrypted); free(buf); } /** * 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)) { log0(0, 0, "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) { sockerror(group->group_id,group->file_id, "Error sending FILEINFO_ACK"); } else { log2(group->group_id, group->file_id, "FILEINFO_ACK sent"); } log3(group->group_id, group->file_id, "send time: %d.%06d", send_time.tv_sec, send_time.tv_usec); free(encrypted); free(buf); } /** * Verifies a server's public key fingerprint */ int verify_server_fingerprint(const unsigned char *keyblob, int bloblen, struct group_list_t *group) { unsigned char fingerprint[HMAC_LEN]; unsigned int fplen; int found, keyidx; if (server_count == 0) { return 1; } for (keyidx = 0, found = 0; (keyidx < server_count) && !found; keyidx++) { if (server_keys[keyidx].uid == group->src_id) { keyidx--; found = 1; } } if (!found) { return 0; } if (!server_keys[keyidx].has_fingerprint) { return 1; } hash(HASH_SHA1, keyblob, bloblen, fingerprint, &fplen); if (memcmp(server_keys[keyidx].fingerprint, fingerprint, fplen)) { return 0; } else { return 1; } } /** * 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))) { log0(group->group_id, 0, "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)) { log0(group->group_id,0, "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)) { log0(group->group_id,0, "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; } /** * 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)) { log0(group->group_id, 0, "Keytype invalid or not supported here"); send_abort(group, "Keytype invalid or not supported here"); return 0; } if (!hash_supported(encinfo->hashtype)) { log0(group->group_id, 0, "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)))) { log0(group->group_id, 0, "Invalid sigtype specified"); send_abort(group, "Invalid sigtype specified"); return 0; } if ((keyextype != KEYEX_RSA) && (keyextype != KEYEX_ECDH_RSA) && (keyextype != KEYEX_ECDH_ECDSA)) { log0(group->group_id, 0, "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)) { log0(group->group_id, 0, "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))) { log0(group->group_id, 0, "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)) { log0(group->group_id, 0, "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) { log0(group->group_id, 0, "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) { log0(group->group_id, 0, "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)))) { log0(group->group_id, 0, "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)) { log0(group->group_id, 0, "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) { log0(group->group_id, 0, "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)) { log0(group->group_id, 0, "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)) { log0(group->group_id, 0, "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)) { log0(group->group_id, 0, "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; } /** * 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; memcpy(&group->multi.sin6.sin6_addr.s6_addr, privatemcast, iplen); } else { group->multi.sin.sin_family = AF_INET; memcpy(&group->multi.sin.sin_addr.s_addr, privatemcast, iplen); } group->fileinfo.fd = -1; if ((announce->hlen * 4U) < sizeof(struct announce_h) + (2U * iplen)) { log0(group->group_id, 0, "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)))) { log0(group->group_id, 0, "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) { log0(group->group_id, 0, "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 (group->robust * group->grtt < 1.0) { add_timeval_d(&group->expire_time, 1.0); } else { add_timeval_d(&group->expire_time, 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; } /** * 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]; 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))) { log0(ntohl(header->group_id), 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), 0, "Name not in host list"); return; } if ((group = find_open_slot()) == NULL ) { log0(ntohl(header->group_id), 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 *)&group->multi, family_len(group->multi), privname, sizeof(privname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log0(group->group_id,0, "Received request from %08X", ntohl(group->src_id)); log1(group->group_id, 0, "Using private multicast address %s", privname); log3(group->group_id, 0, "grtt = %.6f", group->grtt); log3(group->group_id, 0, "send time: %d.%06d", group->last_server_ts.tv_sec, group->last_server_ts.tv_usec); log3(group->group_id, 0, "receive time: %d.%06d", group->last_server_rx_ts.tv_sec, group->last_server_rx_ts.tv_usec); if (group->restart) { if (group->sync_mode) { log0(group->group_id, 0, "Sync mode and restart mode incompatable"); send_abort(group, "Sync mode and restart mode incompatable"); return; } } if (!addr_blank(&group->multi)) { if (server_count > 0) { if (!is_multicast(&group->multi, 1)) { log0(group->group_id, 0, "Invalid source specific multicast address: %s", privname); send_abort(group, "Invalid source specific multicast address"); return; } 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)) { log0(group->group_id, 0, "Invalid multicast address: %s", privname); send_abort(group, "Invalid multicast address"); return; } 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); } /** * 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))) { log1(group->group_id, 0, "Rejecting REG_CONF from server: invalid message size"); return; } addrcnt = (meslen - (regconf->hlen * 4)) / 4; if (uid_in_list(addrlist, addrcnt)) { log2(group->group_id, 0, "Registration confirmed"); group->phase = PHASE_MIDGROUP; set_timeout(group, 0); } if (group->restart) { read_restart_file(group); } } /** * 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))) { log1(group->group_id, 0, "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) { log2(group->group_id, 0, "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)) { log0(group->group_id, 0, "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); } } } uftp-4.1.5/LICENSE.txt0000644000076400007640000010451312137013343013327 0ustar dbushdbush GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . uftp-4.1.5/server_common.c0000644000076400007640000003646712250244021014535 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #ifdef WINDOWS // none #else // if WINDOWS #include #include #include #include #include #endif #include "server.h" #include "server_common.h" /** * Initializes the uftp header of an outgoing packet */ void set_uftp_header(struct uftp_h *header, int func, uint32_t group_id, uint8_t group_inst, double l_grtt, int l_gsize) { header->version = UFTP_VER_NUM; header->func = func; header->src_id = htonl(server_id); header->group_id = htonl(group_id); header->group_inst = group_inst; header->grtt = quantize_grtt(l_grtt); header->gsize = quantize_gsize(l_gsize); } /** * Sends an ABORT message to one or more clients */ void send_abort(const struct finfo_t *finfo, const char *message, const union sockaddr_u *destaddr, uint32_t dest, int encrypt, int current) { 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, finfo->group_id, finfo->group_inst, grtt, destcount); header->seq = htons(send_seq++); abort_hdr->func = ABORT; abort_hdr->hlen = sizeof(struct abort_h) / 4; if (dest) { abort_hdr->host = dest; } else if (current) { abort_hdr->flags |= FLAG_CURRENT_FILE; } strncpy(abort_hdr->message, message, sizeof(abort_hdr->message) - 1); payloadlen = (abort_hdr->hlen * 4); if (encrypt) { encrypted = NULL; if (!encrypt_and_sign(buf, &encrypted, payloadlen, &enclen, keytype, groupkey, groupsalt, &ivctr, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, privkey, privkeylen)) { log0(finfo->group_id, finfo->file_id, "Error encrypting ABORT"); free(buf); return; } outpacket = encrypted; payloadlen = enclen; } else { encrypted = NULL; outpacket = buf; } if (nb_sendto(sock, outpacket, payloadlen + sizeof(struct uftp_h), 0, (const struct sockaddr *)destaddr, family_len(*destaddr)) == SOCKET_ERROR) { sockerror(finfo->group_id, finfo->file_id, "Error sending ABORT"); } free(buf); free(encrypted); } /** * For messages that send a list of clients in the body, append all clients * in the specified state to the packet, then send the message of the given * type when either the body is full or the end of the client list has been * reached. All header fields must be populated before calling. * Returns 1 on success, 0 on fail. */ int send_multiple(const struct finfo_t *finfo, unsigned char *packet, int message, int attempt, uint32_t *idlist, int state, int encrypt, const union sockaddr_u *destaddr, int regconf) { struct uftp_h *header; int hsize, payloadlen, enclen, rval; int maxdest, packetcnt, dests, i; unsigned char *mheader, *encpacket, *outpacket; char out_addr[INET6_ADDRSTRLEN]; header = (struct uftp_h *)packet; mheader = packet + sizeof(struct uftp_h); hsize = (unsigned char *)idlist - mheader; maxdest = blocksize / sizeof(uint32_t); packetcnt = 1; if (encrypt) { encpacket = safe_calloc(MAXMTU + keylen, 1); } else { encpacket = NULL; } for (i = 0, dests = 0; i < destcount; i++) { if (message == REG_CONF) { // Only send REG_CONF for a particular client if either it's // behind a proxy or we're sending them to everyone. // Also, don't send if we already sent one and we haven't // gotten another REGISTER if ((destlist[i].status == state) && (!finfo->deststate[i].conf_sent) && (regconf || (destlist[i].proxyidx != -1))) { idlist[dests++] = destlist[i].id; finfo->deststate[i].conf_sent = 1; } } else if (message == DONE_CONF) { // As with REG_CONF, don't send a DONE_CONF for a client // if we already sent one and we haven't gotten another COMPLETE if ((destlist[i].status == state) && (!finfo->deststate[i].conf_sent)) { idlist[dests++] = destlist[i].id; finfo->deststate[i].conf_sent = 1; } } else if (destlist[i].status == state) { idlist[dests++] = destlist[i].id; } if ((dests >= maxdest) || ((i == destcount - 1) && ((dests > 0) || (message == DONE)))) { header->seq = htons(send_seq++); payloadlen = hsize + (dests * sizeof(uint32_t)); if (message == ANNOUNCE) { outpacket = packet; if (!sign_announce(finfo, outpacket, payloadlen)) { log0(finfo->group_id, finfo->file_id, "Error signing ANNOUNCE"); free(encpacket); return 0; } } else if (encrypt) { if (!encrypt_and_sign(packet, &encpacket, payloadlen, &enclen, keytype, groupkey, groupsalt, &ivctr, ivlen, hashtype, grouphmackey, hmaclen, sigtype, keyextype, privkey, privkeylen)) { log0(finfo->group_id, finfo->file_id, "Error encrypting %s", func_name(message)); free(encpacket); return 0; } outpacket = encpacket; payloadlen = enclen; } else { outpacket = packet; } log2(finfo->group_id, finfo->file_id, "Sending %s %d.%d", func_name(message), attempt, packetcnt); if (log_level >= 4) { rval = getnameinfo((const struct sockaddr *)destaddr, family_len(*destaddr), out_addr, sizeof(out_addr), NULL, 0, NI_NUMERICHOST); if (rval) { log4(finfo->group_id, finfo->file_id, "getnameinfo failed: %s", gai_strerror(rval)); } log4(finfo->group_id,finfo->file_id, "Sending to %s", out_addr); } if (nb_sendto(sock, outpacket, payloadlen + sizeof(struct uftp_h), 0, (const struct sockaddr *)destaddr, family_len(*destaddr)) == SOCKET_ERROR) { sockerror(finfo->group_id, finfo->file_id, "Error sending %s", func_name(message)); sleep(1); free(encpacket); return 0; } if (packet_wait) usleep(packet_wait); memset(idlist, 0, maxdest * sizeof(uint32_t)); dests = 0; packetcnt++; } } free(encpacket); return 1; } /** * Do basic checking on a received packet, like checking the version * and making sure the size matches the size in the header. * Returns 1 on success, 0 on fail. */ int validate_packet(const unsigned char *packet, int len, const struct finfo_t *finfo) { const struct uftp_h *header; header = (const struct uftp_h *)packet; if (header->version != UFTP_VER_NUM) { log1(finfo->group_id, finfo->file_id, "Invalid version %02X", header->version); return 0; } if (header->func == ENCRYPTED) { if (len < sizeof(struct uftp_h) + sizeof(struct encrypted_h)) { log1(finfo->group_id,finfo->file_id, "Invalid packet size %d", len); return 0; } } else { if (len < sizeof(struct uftp_h) + 4) { log1(finfo->group_id,finfo->file_id, "Invalid packet size %d", len); return 0; } } if (ntohl(header->group_id) != finfo->group_id) { log1(finfo->group_id, finfo->file_id, "Invalid group ID %08X, " "expected %08X", ntohl(header->group_id), finfo->group_id); return 0; } if ((header->func == ENCRYPTED) && (keytype == KEY_NONE)) { log1(finfo->group_id, finfo->file_id, "Received encrypted packet with encryption disabled"); return 0; } return 1; } /** * Apply a signature to an ANNOUCE if the key exchange scheme calls for it. * On entry, the packet should be complete other that the signature. * Returns 1 on success, 0 on fail. */ int sign_announce(const struct finfo_t *finfo, unsigned char *packet, int packetlen) { struct announce_h *announce; struct enc_info_he *encinfo; unsigned char *sig, *sigcopy; unsigned int iplen, siglen, _siglen; if ((keyextype != KEYEX_ECDH_RSA) && (keyextype != KEYEX_ECDH_ECDSA)) { return 1; } announce = (struct announce_h *)(packet + sizeof(struct uftp_h)); if (announce->flags & FLAG_IPV6) { iplen = sizeof(struct in6_addr); } else { iplen = sizeof(struct in_addr); } encinfo = (struct enc_info_he *)(packet + sizeof(struct uftp_h) + sizeof(struct announce_h) + iplen + iplen); sig = (unsigned char *)encinfo + sizeof(struct enc_info_he) + ntohs(encinfo->keylen) + ntohs(encinfo->dhlen); siglen = ntohs(encinfo->siglen); memset(sig, 0, siglen); sigcopy = safe_calloc(siglen, 1); if (keyextype == KEYEX_ECDH_ECDSA) { if (!create_ECDSA_sig(privkey.ec, hashtype, packet, packetlen, sigcopy, &_siglen)) { // Called function should log free(sigcopy); return 0; } } else { if (!create_RSA_sig(privkey.rsa, hashtype, packet, packetlen, sigcopy, &_siglen)) { // Called function should log free(sigcopy); return 0; } } if (_siglen != siglen) { log0(finfo->group_id, finfo->file_id, "Signature length doesn't match expected length"); log1(finfo->group_id, finfo->file_id, "expected %d, got %d", siglen, _siglen); free(sigcopy); return 0; } memcpy(sig, sigcopy, siglen); free(sigcopy); return 1; } /** * Look for a given client in the global client list * Returns the client's index in the list, or -1 if not found */ int find_client(uint32_t id) { int i; // TODO: This can be a lot more efficient. Should probably sort by // ID and keep an index, then do a binary search. for (i = 0; i < destcount; i++) { if (destlist[i].id == id) { return i; } } return -1; } /** * Check to see if a client is in an error state * Returns 1 if true, 0 if false */ int client_error(int listidx) { return ((destlist[listidx].status == DEST_MUTE) || (destlist[listidx].status == DEST_LOST) || (destlist[listidx].status == DEST_ABORT)); } /** * Process an ABORT message */ void handle_abort(const unsigned char *message, int meslen, int idx, struct finfo_t *finfo, uint32_t src) { const struct abort_h *abort_hdr; int i; abort_hdr = (const struct abort_h *)message; if (meslen < (abort_hdr->hlen * 4)) { log1(finfo->group_id, finfo->file_id, "Rejecting ABORT from %08X: invalid message size", (idx == -1) ? ntohl(src) : destlist[idx].id); return; } if (idx == -1) { log1(finfo->group_id, finfo->file_id, "Transfer aborted by %08X: %s", ntohl(src), abort_hdr->message); return; } if (abort_hdr->host != 0) { idx = find_client(abort_hdr->host); } if (idx == -1) { log1(finfo->group_id, finfo->file_id, "Transfer aborted by %08X: %s", ntohl(src), abort_hdr->message); } else { destlist[idx].status = DEST_ABORT; log1(finfo->group_id, finfo->file_id, "Transfer aborted by %s: %s", destlist[idx].name, abort_hdr->message); } if (quit_on_error) { log0(finfo->group_id, finfo->file_id, "Aboring all clients"); send_abort(finfo, "A client aborted, aborting all", &receive_dest, 0, 0, 0); // If encryption enabled, send ABORT both encrypted and unencrypted // since we can't be sure what phase we're currently in. if (keytype != KEY_NONE) { send_abort(finfo, "A client aborted, aborting all", &receive_dest, 0, 1, 0); } for (i = 0; i < destcount; i++) { if ((destlist[i].status == DEST_ACTIVE) || (destlist[i].status == DEST_REGISTERED)) { destlist[i].status = DEST_ABORT; } } } } /** * Recalculate the GRTT based on the RTTs of all receivers * Returns 1 if at least one active client is found, 0 if no active clients */ int recalculate_grtt(const struct finfo_t *finfo, int grtt_set, int clear_measured) { double new_grtt; int i, found; for (new_grtt = 0, found = 0, i = 0; i < destcount; i++) { if (!client_error(i) && destlist[i].rtt_measured) { found = 1; if (destlist[i].rtt > new_grtt) { new_grtt = destlist[i].rtt; } } if (clear_measured) { destlist[i].rtt_measured = 0; } destlist[i].rtt_sent = 0; } if (found) { if (new_grtt < min_grtt) { new_grtt = min_grtt; } else if (new_grtt > max_grtt) { new_grtt = max_grtt; } if (grtt_set && (new_grtt < 0.9 * grtt)) { grtt = 0.9 * grtt; } else { grtt = new_grtt; } log3(finfo->group_id, finfo->file_id, "grtt = %.6f", grtt); return 1; } else { return 0; } } uftp-4.1.5/client_common.h0000644000076400007640000000521312250244021014473 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_COMMON_H #define _CLIENT_COMMON_H struct group_list_t *find_group(uint32_t group_id, uint8_t group_inst); int uid_in_list(const uint32_t *addrlist, int size); void read_restart_file(struct group_list_t *group); void run_postreceive_multi(struct group_list_t *group, char *const *files, int count); void run_postreceive(struct group_list_t *group, char *file); void file_cleanup(struct group_list_t *group, int abort); void set_uftp_header(struct uftp_h *header, int func, struct group_list_t *group); void set_timeout(struct group_list_t *group, int rescale); void send_abort(struct group_list_t *group, const char *message); void handle_abort(struct group_list_t *group, const unsigned char *message, unsigned meslen); void send_key_req(void); void handle_proxy_key(const union sockaddr_u *src, unsigned char *message, unsigned meslen); void clear_path(const char *path, struct group_list_t *group); void move_to_backup(struct group_list_t *group); int create_path_to_file(struct group_list_t *group, const char *filename); void update_loss_history(struct group_list_t *group, uint16_t txseq, int size); double loss_event_rate(struct group_list_t *group); unsigned current_cc_rate(struct group_list_t *group); #endif // _CLIENT_COMMON_H uftp-4.1.5/encrypt_none.c0000644000076400007640000001311312250244021014341 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #ifdef WINDOWS typedef unsigned __int8 uint8_t; typedef __int8 int8_t; typedef unsigned __int16 uint16_t; typedef __int16 int16_t; typedef unsigned __int32 uint32_t; typedef __int32 int32_t; typedef unsigned __int64 uint64_t; typedef __int64 int64_t; #else #include #endif #include "encryption.h" void crypto_init(int set_sys_key) { } void crypto_cleanup() { } /** * Returns whether a particular cipher is supported */ int cipher_supported(int keytype) { return 0; } /** * Returns whether a particular hash is supported */ int hash_supported(int hashtype) { return 0; } void get_key_info(int keytype, int *keylen, int *ivlen) { } int get_hash_len(int hashtype) { return 0; } int get_random_bytes(unsigned char *buf, int num) { return 0; } int encrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { return 0; } int decrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { return 0; } int create_hmac(int hashtype, const unsigned char *key, unsigned int keylen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { return 0; } int hash(int hashtype, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { return 0; } int RSA_keylen(const RSA_key_t rsa) { return 0; } int EC_keylen(const EC_key_t ec) { return 0; } int ECDSA_siglen(const EC_key_t ec) { return 0; } int RSA_encrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen) { return 0; } int RSA_decrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen) { return 0; } int create_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen) { return 0; } int verify_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int siglen) { return 0; } int create_ECDSA_sig(EC_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen) { return 0; } int verify_ECDSA_sig(EC_key_t ec, int hashtype, const unsigned char *mes, unsigned int meslen, const unsigned char *sig, unsigned int siglen) { return 0; } int get_ECDH_key(EC_key_t pubkey, EC_key_t privkey, unsigned char *key, unsigned int *keylen) { return 0; } int import_RSA_key(RSA_key_t *rsa, const unsigned char *keyblob, uint16_t bloblen) { return 0; } int export_RSA_key(const RSA_key_t rsa, unsigned char *keyblob, uint16_t *bloblen) { return 0; } int import_EC_key(EC_key_t *ec, const unsigned char *keyblob, uint16_t bloblen, int isdh) { return 0; } int export_EC_key(const EC_key_t ec, unsigned char *keyblob, uint16_t *bloblen) { return 0; } RSA_key_t gen_RSA_key(int bits, int exponent, const char *filename) { return NULL; } RSA_key_t read_RSA_key(const char *filename) { return NULL; } EC_key_t gen_EC_key(uint8_t curve, int isdh, const char *filename) { return NULL; } EC_key_t read_EC_key(const char *filename) { return NULL; } union key_t read_private_key(const char *filename, int *keytype) { union key_t key; key.key = 0; *keytype = 0; return key; } uint8_t get_EC_curve(const EC_key_t ec) { return 0; } void free_RSA_key(RSA_key_t rsa) { } void free_EC_key(EC_key_t ec) { } const char *get_next_container() { return NULL; } void delete_container(const char *name) { } void set_sys_keys(int set) { } uftp-4.1.5/proxy_downstream.c0000644000076400007640000005233312250244021015271 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include "win_func.h" #else #include #include #include #include #include #endif #include "proxy.h" #include "proxy_common.h" #include "proxy_downstream.h" /** * Adds a client to the given group */ int add_client(uint32_t id, struct pr_group_list_t *group) { struct pr_destinfo_t *dest; dest = &group->destinfo[group->destcount]; snprintf(dest->name, sizeof(dest->name), "0x%08X", ntohl(id)); dest->id = id; dest->pending = -1; return group->destcount++; } /** * For a given client, calculate the master key and do key expansion * to determine the symmetric cypher key and IV salt, and hash key */ void calculate_client_keys(struct pr_group_list_t *group, int hostidx) { unsigned char *seed, *prf_buf; int explen, len, seedlen; struct pr_destinfo_t *dest; dest = &group->destinfo[hostidx]; explen = group->keylen + group->ivlen + group->hmaclen; seedlen = sizeof(group->rand1) * 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), dest->rand2, sizeof(dest->rand2)); PRF(group->hashtype, MASTER_LEN, dest->premaster, dest->premaster_len, "master secret", seed, seedlen, prf_buf, &len); memcpy(dest->master,prf_buf, sizeof(dest->master)); PRF(group->hashtype, explen, dest->master, sizeof(dest->master), "key expansion", seed, seedlen, prf_buf, &len); memcpy(dest->hmackey, prf_buf, group->hmaclen); memcpy(dest->key, prf_buf + group->hmaclen, group->keylen); memcpy(dest->salt, prf_buf + group->hmaclen + group->keylen, group->ivlen); free(seed); free(prf_buf); } /** * 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)) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "Rejecting REGISTER from %s: " "failed to decrypt premaster secret", dest->name); return 0; } if (len != MASTER_LEN) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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 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) { log2(group->group_id, 0, "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))) { log2(group->group_id, 0, "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; } } log2(group->group_id, 0, "Received REGISTER%s from %s", dupmsg ? "+" : "", dest->name); if (dest->state == PR_CLIENT_REGISTERED) { check_pending(group, hostidx, message); } } /** * 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) { log2(group->group_id, 0, "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))) { log2(group->group_id, 0, "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))) { log2(group->group_id, 0, "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))) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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; } log2(group->group_id, 0, "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 extention of REGISTER. struct register_h reg; reg.func = REGISTER; check_pending(group, hostidx, (unsigned char *)®); } } /** * 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))) { log2(group->group_id, 0, "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))) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "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); log2(group->group_id, 0, "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 FILEINFO_ACK message from a client */ void handle_fileinfo_ack(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen) { const struct fileinfoack_h *fileinfoack; struct pr_destinfo_t *dest; fileinfoack = (const struct fileinfoack_h *)message; dest = &group->destinfo[hostidx]; if ((meslen < (fileinfoack->hlen * 4U)) || ((fileinfoack->hlen * 4U) < sizeof(struct fileinfoack_h))) { log2(group->group_id, ntohs(fileinfoack->file_id), "Rejecting FILEINFO_ACK from %s: invalid message size", dest->name); return; } log2(group->group_id, ntohs(fileinfoack->file_id), "Received FILEINFO_ACK from %s", dest->name); check_pending(group, hostidx, message); } /** * 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)) { log2(group->group_id, 0, "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)); log2(group->group_id, 0, "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) { sockerror(group->group_id, 0, "Error sending KEYINFO"); free(buf); free(iv); return; } // TODO: This value is good for around 100Mbps. This is under the // assumtion 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++; } } group->keyinfo_cnt++; set_timeout(group, 0, 0); free(buf); free(iv); } /** * Handles an incoming STATUS message from a client */ void handle_status(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen) { const struct status_h *status; int mes_section; struct pr_destinfo_t *dest; status = (const struct status_h *)message; mes_section = ntohs(status->section); dest = &group->destinfo[hostidx]; if ((meslen < (status->hlen * 4U)) || ((status->hlen * 4U) < sizeof(struct status_h))) { log2(group->group_id, ntohs(status->file_id), "Rejecting STATUS from %s: invalid message size", dest->name); return; } log2(group->group_id, ntohs(status->file_id), "Got STATUS for section %d from %s", mes_section, dest->name); check_pending(group, hostidx, message); } /** * Handles an incoming COMPLETE message from a client */ void handle_complete(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen) { const struct complete_h *complete; struct pr_destinfo_t *dest; int alldone, i; char status[20]; complete = (const struct complete_h *)message; dest = &group->destinfo[hostidx]; if ((meslen < (complete->hlen * 4U)) || ((complete->hlen * 4U) < sizeof(struct complete_h))) { log2(group->group_id, ntohs(complete->file_id), "Rejecting COMPLETE from %s: invalid message size", dest->name); return; } switch (complete->status) { case COMP_STAT_NORMAL: strncpy(status, "", sizeof(status)); break; case COMP_STAT_SKIPPED: strncpy(status, "(skipped)", sizeof(status)); break; case COMP_STAT_OVERWRITE: strncpy(status, "(overwritten)", sizeof(status)); break; case COMP_STAT_REJECTED: strncpy(status, "(rejected)", sizeof(status)); break; } log2(group->group_id, ntohs(complete->file_id), "Received COMPLETE%s from %s", status, dest->name); if (ntohs(complete->file_id) == 0) { dest->state = PR_CLIENT_DONE; for (alldone = 1, i = 0; (i < group->destcount) && alldone; i++) { alldone = alldone && (group->destinfo[i].state == PR_CLIENT_DONE); } if (alldone) { group->phase = PR_PHASE_DONE; } } check_pending(group, hostidx, message); } uftp-4.1.5/server_init.c0000644000076400007640000004335712250244021014204 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include #else // if WINDOWS #include #include #include #include #include #endif #include "server.h" #include "server_config.h" #include "server_init.h" /** * Cleanup routine set up by atexit */ void cleanup(void) { int i; closesocket(sock); for (i = 0; i < destcount; i++) { if (keytype != KEY_NONE) { if (destlist[i].encinfo) { if (destlist[i].encinfo->pubkey.key) { if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { free_RSA_key(destlist[i].encinfo->pubkey.rsa); } else { free_EC_key(destlist[i].encinfo->pubkey.ec); } if ((keyextype == KEYEX_ECDH_RSA) || (keyextype == KEYEX_ECDH_ECDSA)) { free_EC_key(destlist[i].encinfo->dhkey.ec); } } free(destlist[i].encinfo); } } if (destlist[i].clients) { free(destlist[i].clients); } } if (keytype != KEY_NONE) { if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { free_RSA_key(privkey.rsa); } else { free_EC_key(privkey.ec); } if ((keyextype == KEYEX_ECDH_RSA) || (keyextype == KEYEX_ECDH_ECDSA)) { free_EC_key(dhkey.ec); } } crypto_cleanup(); if (status_file) { fclose(status_file); } #ifdef WINDOWS WSACleanup(); #endif } /** * Generic signal handler, sets user_abort flag */ void gotsig(int sig) { log0(0, 0, "Got signal %d, aborting", sig); user_abort = 1; } #ifdef WINDOWS /** * Windows event handler, sets user_abort flag */ BOOL WINAPI winsig(DWORD event) { switch (event) { case CTRL_C_EVENT: log0(0, 0, "Got CTRL_C_EVENT"); break; case CTRL_BREAK_EVENT: log0(0, 0, "Got CTRL_BREAK_EVENT"); break; case CTRL_CLOSE_EVENT: log0(0, 0, "Got CTRL_CLOSE_EVENT"); break; case CTRL_LOGOFF_EVENT: log0(0, 0, "Got CTRL_LOGOFF_EVENT"); break; case CTRL_SHUTDOWN_EVENT: log0(0, 0, "Got CTRL_SHUTDOWN_EVENT"); break; default: log0(0, 0, "GOT unknown event", event); break; } user_abort = 1; return TRUE; } #endif /** * Do initial setup before parsing arguments, including getting interface list */ void pre_initialize(void) { #ifdef WINDOWS struct WSAData data; if (WSAStartup(2, &data)) { fprintf(stderr, "Error in WSAStartup: %d\n", WSAGetLastError()); exit(1); } #endif applog = stderr; ifl_len = sizeof(ifl) / sizeof(struct iflist); getiflist(ifl, &ifl_len); srand((unsigned int)time(NULL) ^ getpid()); crypto_init(0); } /** * Do all socket creation and initialization */ void create_sockets(void) { struct addrinfo ai_hints, *ai_rval; char *p, tmp_multi[INET6_ADDRSTRLEN]; int found_if, fdflag, bcast, rval, i; // Set up global sockaddr_u structs for public and private addresses // Perform octet substitution on private multicast address // Make sure public, private, and interface addrs are the same IP version memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(pub_multi, port, &ai_hints, &ai_rval)) != 0) { log0(0, 0, "Invalid public address or port: %s", gai_strerror(rval)); exit(1); } memcpy(&listen_dest, ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); if (!is_multicast(&listen_dest, 0)) { receive_dest = listen_dest; } else { if (listen_dest.ss.ss_family == AF_INET6) { while ((p = strchr(priv_multi,'x')) != NULL) { memset(tmp_multi, 0, sizeof(tmp_multi)); snprintf(tmp_multi, sizeof(tmp_multi), "%.*s%x%s", (int)(p - priv_multi), priv_multi, rand() & 0xFFFF, p+1); strncpy(priv_multi, tmp_multi, sizeof(priv_multi)); priv_multi[sizeof(priv_multi)-1] = '\x0'; } } else { while ((p = strchr(priv_multi,'x')) != NULL) { memset(tmp_multi, 0, sizeof(tmp_multi)); snprintf(tmp_multi, sizeof(tmp_multi), "%.*s%d%s", (int)(p - priv_multi), priv_multi, rand() & 0xFF, p+1); strncpy(priv_multi, tmp_multi, sizeof(priv_multi)); priv_multi[sizeof(priv_multi)-1] = '\x0'; } } if ((rval = getaddrinfo(priv_multi, port, &ai_hints, &ai_rval)) != 0) { log0(0, 0, "Invalid private address: %s", gai_strerror(rval)); exit(1); } memcpy(&receive_dest, ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); } if (!strcmp(out_if.name, "")) { for (i = 0, found_if = 0; (i < ifl_len) && !found_if; i++) { if ((ifl[i].su.ss.ss_family == listen_dest.ss.ss_family) && (!ifl[i].isloopback)) { found_if = 1; out_if = ifl[i]; } } if (!found_if) { for (i = 0, found_if = 0; (i < ifl_len) && !found_if; i++) { if (ifl[i].su.ss.ss_family == listen_dest.ss.ss_family) { found_if = 1; out_if = ifl[i]; } } } if (!found_if) { log0(0, 0, "ERROR: no network interface found for family"); exit(1); } } if (out_if.su.ss.ss_family == AF_INET6) { server_id = out_if.su.sin6.sin6_addr.s6_addr[12] << 24; server_id |= out_if.su.sin6.sin6_addr.s6_addr[13] << 16; server_id |= out_if.su.sin6.sin6_addr.s6_addr[14] << 8; server_id |= out_if.su.sin6.sin6_addr.s6_addr[15]; } else { server_id = ntohl(out_if.su.sin.sin_addr.s_addr); } if (listen_dest.ss.ss_family != receive_dest.ss.ss_family) { log0(0, 0, "IP version mismatch between public and private addresses"); exit(1); } if (listen_dest.ss.ss_family != out_if.su.ss.ss_family) { log0(0,0, "IP version mismatch between public and interface addresses"); exit(1); } // Create and bind socket if ((sock = socket(listen_dest.ss.ss_family, SOCK_DGRAM, 0)) == INVALID_SOCKET) { sockerror(0, 0, "Error creating socket"); exit(1); } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = listen_dest.ss.ss_family; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; if ((rval = getaddrinfo(NULL, srcport, &ai_hints, &ai_rval)) != 0) { log0(0, 0, "Error getting bind address: %s", gai_strerror(rval)); exit(1); } if (bind(sock, ai_rval->ai_addr, ai_rval->ai_addrlen) == SOCKET_ERROR) { sockerror(0, 0, "Error binding socket"); exit(1); } freeaddrinfo(ai_rval); // Set send/receive buffer size, ttl, and multicast interface if (rcvbuf) { if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting receive buffer size"); exit(1); } if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting send buffer size"); exit(1); } } else { rcvbuf = DEF_RCVBUF; if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { rcvbuf = DEF_BSD_RCVBUF; if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting receive buffer size"); exit(1); } } rcvbuf = DEF_RCVBUF; if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { rcvbuf = DEF_BSD_RCVBUF; if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting send buffer size"); exit(1); } } } bcast = 1; if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&bcast, sizeof(bcast)) == SOCKET_ERROR) { sockerror(0, 0, "Error enabling broadcast"); closesocket(sock); exit(1); } if (listen_dest.ss.ss_family == AF_INET6) { #ifdef IPV6_MTU_DISCOVER { int mtuflag = IP_PMTUDISC_DONT; if (setsockopt(sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (char *)&mtuflag, sizeof(mtuflag)) == SOCKET_ERROR) { sockerror(0, 0, "Error disabling MTU discovery"); closesocket(sock); exit(1); } } #endif if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, sizeof(ttl)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting ttl"); closesocket(sock); exit(1); } #if defined IPV6_TCLASS && !defined WINDOWS if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, (char *)&dscp, sizeof(dscp)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting dscp"); closesocket(sock); exit(1); } #endif if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&out_if.ifidx, sizeof(int)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting outgoing interface"); closesocket(sock); exit(1); } } else { char l_ttl = ttl & 0xFF; #ifdef IP_MTU_DISCOVER { int mtuflag = IP_PMTUDISC_DONT; if (setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, (char *)&mtuflag, sizeof(mtuflag)) == SOCKET_ERROR) { sockerror(0, 0, "Error disabling MTU discovery"); closesocket(sock); exit(1); } } #endif if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &l_ttl, sizeof(l_ttl)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting ttl"); closesocket(sock); exit(1); } if (setsockopt(sock, IPPROTO_IP, IP_TOS, (char *)&dscp, sizeof(dscp)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting dscp"); closesocket(sock); exit(1); } if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&out_if.su.sin.sin_addr, sizeof(out_if.su.sin.sin_addr)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting outgoing interface"); closesocket(sock); exit(1); } } // Make socket non-blocking #ifndef BLOCKING #ifdef WINDOWS fdflag = 1; if (ioctlsocket(sock, FIONBIO, &fdflag) == SOCKET_ERROR) { sockerror(0, 0, "Error setting non-blocking option"); closesocket(sock); exit(1); } #else if ((fdflag = fcntl(sock, F_GETFL)) == SOCKET_ERROR) { sockerror(0, 0, "Error getting socket descriptor flags"); closesocket(sock); exit(1); } fdflag |= O_NONBLOCK; if (fcntl(sock, F_SETFL, fdflag) == SOCKET_ERROR) { sockerror(0, 0, "Error setting non-blocking option"); closesocket(sock); exit(1); } #endif #endif // BLOCKING } /** * Initialize crypto library, generate keys */ void key_init(void) { unsigned char *prf_buf; time_t t; uint32_t t2; int explen, len; if (keytype == KEY_NONE) { return; } set_sys_keys(sys_keys); get_key_info(keytype, &keylen, &ivlen); hmaclen = get_hash_len(hashtype); memset(groupkey, 0, sizeof(groupkey)); memset(groupsalt, 0, sizeof(groupsalt)); memset(grouphmackey, 0, sizeof(grouphmackey)); if (!get_random_bytes(groupmaster, sizeof(groupmaster))) { log0(0, 0, "Failed to generate group master"); exit(1); } groupmaster[0] = UFTP_VER_NUM; if (!get_random_bytes(rand1, sizeof(rand1))) { log0(0, 0, "Failed to generate rand1"); exit(1); } // Sets the first 4 bytes of rand1 to the current time t = time(NULL); t2 = (uint32_t)(t & 0xFFFFFFFF); *(uint32_t *)rand1 = t2; explen = hmaclen + keylen + SALT_LEN; prf_buf = safe_calloc(explen + hmaclen, 1); PRF(hashtype, explen, groupmaster, sizeof(groupmaster), "key expansion", rand1, sizeof(rand1), prf_buf, &len); memcpy(grouphmackey, prf_buf, hmaclen); memcpy(groupkey, prf_buf + hmaclen, keylen); memcpy(groupsalt, prf_buf + hmaclen + keylen, SALT_LEN); ivctr = 0; free(prf_buf); if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { if ((!strcmp(keyfile, "")) || (newkeylen != 0)) { privkey.rsa = gen_RSA_key(newkeylen, RSA_EXP, keyfile); } else { privkey.rsa = read_RSA_key(keyfile); } if (!privkey.key) { log0(0, 0, "Failed to read/generate private key"); exit(1); } privkeylen = RSA_keylen(privkey.rsa); } else { if ((!strcmp(keyfile, "")) || (ecdsa_curve != 0)) { privkey.ec = gen_EC_key(ecdsa_curve, 0, keyfile); } else { privkey.ec = read_EC_key(keyfile); } if (!privkey.key) { log0(0, 0, "Failed to read/generate private key"); exit(1); } privkeylen = ECDSA_siglen(privkey.ec); } if ((keyextype == KEYEX_ECDH_RSA) || (keyextype == KEYEX_ECDH_ECDSA)) { dhkey.ec = gen_EC_key(ecdh_curve, 1, NULL); if (!dhkey.key) { log0(0, 0, "Failed to generate DH key"); exit(1); } } } /** * Initialization based on command line args */ void initialize(void) { atexit(cleanup); init_log_mux = 1; init_log(0); #ifdef WINDOWS SetConsoleCtrlHandler(winsig, TRUE); #else { struct sigaction act; sigfillset(&act.sa_mask); act.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; act.sa_handler = gotsig; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGPIPE, &act, NULL); act.sa_handler = SIG_IGN; sigaction(SIGCHLD, &act, NULL); } #endif if (strcmp(statusfilename, "")) { if ((status_file = fopen(statusfilename, "at")) == NULL) { perror("Can't open status file"); exit(1); } } key_init(); create_sockets(); // Size of data packet, used in transmission speed calculations datapacketsize = blocksize + sizeof(struct fileseg_h); if (cc_type == CC_TFMCC) { datapacketsize += sizeof(struct tfmcc_data_info_he); } if (keytype != KEY_NONE) { datapacketsize += ((sigtype == SIG_KEYEX) ? privkeylen : (sigtype == SIG_HMAC) ? hmaclen : 0) + KEYBLSIZE + sizeof(struct encrypted_h); } // 8 = UDP size, 20 = IPv4 size, 40 = IPv6 size if (listen_dest.ss.ss_family == AF_INET6) { datapacketsize += sizeof(struct uftp_h) + 8 + 40; } else { datapacketsize += sizeof(struct uftp_h) + 8 + 20; } // Never ask for a client key with no encryption, // and always ask with RSA/ECDSA signatures if (keytype == KEY_NONE) { client_auth = 0; } else if (sigtype == SIG_KEYEX) { client_auth = 1; } if (cc_type == CC_NONE || cc_type == CC_UFTP3) { if (rate == -1) { packet_wait = 0; } else { packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); } } else if (cc_type == CC_TFMCC) { // Initialize the rate to the default rate for control message timing packet_wait = (int32_t)(1000000.0 * datapacketsize / rate); } } uftp-4.1.5/proxy_common.h0000644000076400007640000000705512250244021014404 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_COMMON_H #define _PROXY_COMMON_H struct pr_group_list_t *find_group(uint32_t group_id, uint8_t group_inst); int find_client(struct pr_group_list_t *group, uint32_t addr); void group_cleanup(struct pr_group_list_t *group); void set_uftp_header(struct uftp_h *header, int func, struct pr_group_list_t *group); void set_timeout(struct pr_group_list_t *group, int pending_reset, int rescale); int max_msg_dest(struct pr_group_list_t *group, int func, int hlen); void send_all_pending(struct pr_group_list_t *group); void check_pending(struct pr_group_list_t *group, int hostdix, const unsigned char *message); int load_pending(struct pr_group_list_t *group, int pendidx, int func, uint32_t *addrlist, int listlen); int check_unfinished_clients(struct pr_group_list_t *group, int abort); void forward_message(struct pr_group_list_t *group, const union sockaddr_u *src, unsigned char *packet, int packetlen); void handle_hb_request(const union sockaddr_u *src, unsigned char *packet, unsigned packetlen); void handle_key_req(const union sockaddr_u *src, const unsigned char *packet, unsigned packetlen); void handle_abort(struct pr_group_list_t *group, const union sockaddr_u *src, const unsigned char *message, unsigned meslen, uint32_t src_id); void send_upstream_abort(struct pr_group_list_t *group, uint32_t dest_id, const char *message); void send_downstream_abort(struct pr_group_list_t *group, uint32_t dest_id, const char *message, int current); void send_hb_response(const union sockaddr_u *src, int response); void send_proxy_key(void); int verify_fingerprint(const struct fp_list_t *fplist, int listlen, const unsigned char *keyblob, uint16_t bloblen, struct pr_group_list_t *group, uint32_t id); uint8_t *build_verify_data(struct pr_group_list_t *group, int hostidx, int *verifylen, int full); void add_naks_to_pending(struct pr_group_list_t *group, int pendidx, const unsigned char *message); #endif // _PROXY_COMMON_H uftp-4.1.5/uftp_keymgt.10000644000076400007640000000760512176066071014141 0ustar dbushdbush.TH uftp_keymgt 1 "30 July 2013" "UFTP 4.1" .SH NAME uftp_keymgt - Encrypted UDP based ftp with multicast - key management utility .SH SYNOPSIS uftp_keymgt [ -m ] [ key_file [ key_file ...] ] uftp_keymgt [ -m ] -g { rsa:key_length | ec:curve } key_file uftp_keymgt [ -m ] -d key_file .SH DESCRIPTION .P uftp_keymgt is a utility for creating, viewing, and deleting RSA and EC private keys used by the UFTP suite. Although keys can be generated on the fly by uftp(1), uftpd(1), and uftpproxyd(1), this utility gives a more straightforward way of doing so without having to kick off a dummy process just to create/view a key. The definition of key_file is dependent on the crypto library UFTP is compiled to use. On Windows systems, UFTP can built to use either CNG, which is the new API supported by Windows Vista and Windows 7, or CryptoAPI, which is the legacy API and the only one available to Windows XP. Under CryptoAPI, all RSA private keys must be stored in a key container (technically only keys used to sign data, but for UFTP\(aqs purposes this is the case). Key containers are internal to Windows, and each user (and the system) has its own set of key containers. In this case, key_file is actually the name of the key container. Elliptic Curve algorithms are not supported under CryptoAPI. Under CNG, RSA and ECDSA private keys are also stored in key containers, and RSA keys created by CrypoAPI may be read by CNG. Like CryptoAPI, key_file also specifies the key container name. CNG only supports 3 named EC curves: prime256v1, secp384r1, and secp521r1. All other systems use OpenSSL for the crypto library (although under Windows UFTP can be also be built to use it). In this case, key_file specifies a file name where the RSA or ECDSA private key is stored unencrypted in PEM format (the OS is expected to protect this file). The -g option is used to generate a key of a given type and store it in the given key_file. The key size and key fingerprint are then printed. Under OpenSSL, a key can actually be generated by the openssl(1) command line utility, although you\(aqll still need to run uftp_keymgt on it to see the fingerprint. The -d option is available only on Windows systems using CryptoAPI or CNG. It deletes the key container specified by key_file. When neither -g or -d are specified, the type and fingerprint of all keys listed are printed. If no keys are specified under Windows with CryptoAPI or CNG, the private key for all key containers for the current user are printed. Note that some key containers may exist that an application other than UFTP is using, and some of those may not have an RSA or EC private key. .SH OPTIONS .P The following options are supported: .TP .B \-g { rsa:key_length | ec:curve } key_file Specifies the type of new key and the key_file to store it in. New keys are specified as either rsa:key_length, which creates an RSA private key key_length bits wide, or as ec:curve, which creates an EC key using the curve "curve". The list of supported EC curves is as follows (availability may vary depending on system settings and crypto library used): sect163k1 sect163r1 sect163r2 sect193r1 sect193r2 sect233k1 sect233r1 sect239k1 sect283k1 sect283r1 sect409k1 sect409r1 sect571k1 sect571r1 secp160k1 secp160r1 secp160r2 secp192k1 prime192v1 secp224k1 secp224r1 secp256k1 prime256v1 secp384r1 secp521r1 .TP .B \-d key_file Specifies the Windows key container to delete. .TP .B \-m For Windows systems using CryptoAPI or CNG, private keys are normally stored in the key container of the running user. Specifying this option stores keys in the system key container. On non-Windows systems, this option has no effect. .SH SEE ALSO uftp(1), uftpd(1), uftpproxyd(1) .SH NOTES .P The latest version of UFTP can be found at http://uftp-multicast.sourceforge.net. UFTP is covered by the GNU General Public License. Commercial licenses and support are available from Dennis Bush (bush@tcnj.edu). uftp-4.1.5/client_config.h0000644000076400007640000000511212176066071014464 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_CONFIG_H #define _CLIENT_CONFIG_H #define DEF_PORT "1044" #define DEF_PUB_MULTI "230.4.4.1" #define DEF_RCVBUF 262144 #define DEF_BSD_RCVBUF 233016 #define DEF_DSCP 0 #define DEF_HB_INT 20 #ifdef WINDOWS #define DEF_LOGFILE "C:\\uftpd_log.txt" #define DEF_DESTDIR "C:\\temp" #elif defined VMS #define DEF_LOGFILE "SYS$SCRATCH:uftpd_log.txt" #define DEF_DESTDIR "SYS$SCRATCH" #else #define DEF_LOGFILE "/tmp/uftpd.log" #define DEF_DESTDIR "/tmp" #endif #define DEF_TEMPDIR "" #define USAGE "uftpd [ -d ] [ -p port ] [ -B buf_size ]\n\ [ -E ] [ -Q dscp ] [ -U UID ] [ -x log_level ] [ -t ] [ -T temp_dir ]\n\ [ -D dest_dir [ dest_dir... ]] [ -A backup_dir [ backup_dir... ]]\n\ [ -s postreceive_script] [ -L logfile ] [] [ -P pidfile ]\n\ [ -S serverlist_file ] [ -R proxy[/fp] ] [ -k keyfile[,keyfile...] ]\n\ [ -K rsa:key_length | ec:curve[,rsa:key_length | ec:curve...]]\n\ [ -m ] [ -N priority ] [ -i ] [ -g max_log_size ] [ -n max_log_count ]\n\ [ -H hb_server[:port][,hb_server[:port]...] ] [ -h hb_interval ]\n\ [ -I interface[,interface...] ] [ -M pub_mcast_addr[,pub_mcast_addr...] ]\n" void process_args(int argc, char *argv[]); #endif // _CLIENT_CONFIG_H uftp-4.1.5/server_transfer.h0000644000076400007640000000457612250244021015072 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_TRANSFER_H #define _SERVER_TRANSFER_H #include "server.h" int send_doneconf(const struct finfo_t *finfo, int attempt); int send_done(const struct finfo_t *finfo, int attempt, int section, double l_grtt); void create_cc_list(unsigned char **body, int *len); void send_cong_ctrl(const struct finfo_t *finfo, double l_grtt, uint16_t l_cc_seq, uint32_t l_cc_rate, unsigned char *body, int len); void handle_complete(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx); void handle_status(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx, int *got_naks); void handle_cc_ack(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx); int send_data(const struct finfo_t *finfo, unsigned char *packet, int datalen, unsigned char *encpacket); void print_status(const struct finfo_t *finfo, struct timeval start_time); #endif // _SERVER_TRANSFER_H uftp-4.1.5/client_transfer.h0000644000076400007640000000447212250244021015035 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_TRANSFER_H #define _CLIENT_TRANSFER_H void handle_fileseg(struct group_list_t *group, const unsigned char *message, unsigned meslen, uint16_t txseq); int file_done(struct group_list_t *group, int detail); unsigned int get_naks(struct group_list_t *group, unsigned int section, unsigned char **naks); void handle_done(struct group_list_t *group, const unsigned char *message, unsigned meslen); void handle_done_conf(struct group_list_t *group, const unsigned char *message, unsigned meslen); void handle_cong_ctrl(struct group_list_t *group, const unsigned char *message, unsigned meslen, struct timeval rxtime); void send_status(struct group_list_t *group, unsigned int section, const unsigned char *naks, unsigned int nak_count); void send_complete(struct group_list_t *group, int freespace); void send_cc_ack(struct group_list_t *group); #endif // _CLIENT_TRANSFER_H uftp-4.1.5/encrypt_cryptoapi.c0000644000076400007640000007263412250244021015431 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include "uftp_common.h" #include "encryption.h" #define MAXLIST 100 #define BLOBLEN 1000 struct private_key_list_t { HCRYPTPROV provider; HCRYPTKEY key; }; struct key_list_t { char keystr[MAXKEY]; int keylen; HCRYPTKEY mskey; }; static const struct keyinfo_t { ALG_ID alg; int keysize; int blocksize; } keyinfo[] = { { CALG_DES, 8, 8 }, { CALG_3DES, 24, 8 }, #ifdef CALG_AES_128 { CALG_AES_128, 16, 16 }, #endif #ifdef CALG_AES_256 { CALG_AES_256, 32, 16} #endif }; static const struct hashinfo_t { ALG_ID alg; int hashsize; } hashinfo[] = { { CALG_MD5, 16 }, { CALG_SHA1, 20 }, #ifdef CALG_SHA_256 { CALG_SHA_256, 32 } #endif }; // The CSP used for all non-signing operations static HCRYPTPROV base_prov; // The provider type of the best available CSP static DWORD prov_type; // Since using an RSA key for signing must be done via the provider and // not directly with the key, each is in its own key container with its // own separate provider. The user passes in the key to sign with, and this // list matches the key with the provider that has access to it for signing. static struct private_key_list_t private_key_list[MAXLIST]; // Since symmetric keys and hmac keys get an HCRYPTKEY associated with them, // the first time the user wants to use a key a HCRYPTKEY is created. Then // on subsequent uses the associated HCRYPTKEY is looked up so it doesn't // have to be created and destroyed every time it's used. static struct key_list_t symmetric_keys[MAXLIST], hmac_keys[MAXLIST]; static int machine_keyset = 0; /** * Prints Microsoft specific error messages to log */ static void mserror(const char *str) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); clog0(0, 0, "%s: (0x%08X) %s", str, GetLastError(), errbuf); } static int init_done = 0; /** * Performs all necessary steps to initialize the crypto library */ void crypto_init(int set_sys_key) { DWORD tmp_prov_type, name_len; int cnt, found_aes, i; // First see if we have an AES capable provider. // If so, use that, otherwise use one without. #ifdef PROV_RSA_AES found_aes = 0; cnt = 0; name_len = 0; while (CryptEnumProviderTypes(cnt, 0, 0, &tmp_prov_type, NULL, &name_len) && (!found_aes)) { if (tmp_prov_type == PROV_RSA_AES) { found_aes = 1; } cnt++; name_len = 0; } if ((!found_aes) && (GetLastError() != ERROR_NO_MORE_ITEMS)) { mserror("CryptEnumProviderTypes failed"); exit(1); } prov_type = (found_aes ? PROV_RSA_AES : PROV_RSA_FULL); #else prov_type = PROV_RSA_FULL; #endif if (set_sys_key) { machine_keyset = CRYPT_MACHINE_KEYSET; } else { machine_keyset = 0; } if (!CryptAcquireContext(&base_prov, NULL, NULL, prov_type, CRYPT_VERIFYCONTEXT | machine_keyset)) { mserror("CryptAcquireContext failed"); exit(1); } for (i = 0; i < MAXLIST; i++) { memset(&private_key_list[i], 0, sizeof(private_key_list[i])); memset(&symmetric_keys[i], 0, sizeof(symmetric_keys[i])); memset(&hmac_keys[i], 0, sizeof(hmac_keys[i])); } init_done = 1; } /** * Performs all necessary steps to clean up the crypto library */ void crypto_cleanup(void) { int i; if (!init_done) { return; } for (i = 0; i < MAXLIST; i++) { if (private_key_list[i].provider != 0) { // Cleanup of private (and public) keys should be done by caller, // since they have a handle to the key under CryptoAPI and OpenSSL if (!CryptReleaseContext(private_key_list[i].provider, 0)) { mserror("CryptReleaseContext failed"); } } if (symmetric_keys[i].keylen != 0) { if (!CryptDestroyKey(symmetric_keys[i].mskey)) { mserror("CryptDestroyKey failed"); } } if (hmac_keys[i].keylen != 0) { if (!CryptDestroyKey(hmac_keys[i].mskey)) { mserror("CryptDestroyKey failed"); } } } if (!CryptReleaseContext(base_prov, 0)) { mserror("CryptReleaseContext failed"); } } /** * Returns the next key container for the current user */ const char *get_next_container(void) { static int flag = CRYPT_FIRST; static char *item = NULL; static int mlen = 0; int rval, len; if (flag == CRYPT_FIRST) { rval = CryptGetProvParam(base_prov, PP_ENUMCONTAINERS, NULL, &mlen, CRYPT_FIRST); if (!rval) { return NULL; } item = safe_malloc(mlen); } len = mlen; rval = CryptGetProvParam(base_prov, PP_ENUMCONTAINERS, item, &len, flag); if (!rval) { if (GetLastError() != ERROR_NO_MORE_ITEMS) { mserror("CryptGetProvParam failed"); } flag = CRYPT_FIRST; free(item); item = NULL; return NULL; } flag = CRYPT_NEXT; return item; } /** * Deletes the key container with the given name */ void delete_container(const char *name) { HCRYPTPROV prov; if (!CryptAcquireContext(&prov, name, NULL, prov_type, CRYPT_DELETEKEYSET | machine_keyset)) { mserror("CryptAcquireContext for delete failed"); } } /** * Returns the ALG_ID associated with a given keytype */ static ALG_ID get_cipher(int keytype) { switch (keytype) { case KEY_DES: return CALG_DES; case KEY_DES_EDE3: return CALG_3DES; case KEY_AES128_CBC: #ifdef CALG_AES_128 return CALG_AES_128; #else return 0; #endif case KEY_AES256_CBC: #ifdef CALG_AES_256 return CALG_AES_256; #else return 0; #endif case KEY_AES128_GCM: case KEY_AES256_GCM: case KEY_AES128_CCM: case KEY_AES256_CCM: // Not supported by CryptoAPI return 0; default: log0(0, 0, "Unknown keytype: %d", keytype); return 0; } } /** * Returns the ALG_ID associated with a given hashtype */ static ALG_ID get_hash(int hashtype) { switch (hashtype) { case HASH_SHA256: #ifdef CALG_SHA_256 return CALG_SHA_256; #else return 0; #endif case HASH_SHA1: return CALG_SHA1; case HASH_MD5: return CALG_MD5; default: log0(0, 0, "Unknown hashtype: %d", hashtype); return 0; } } /** * Returns whether a particular ALG_ID is available in the current CSP */ static int alg_found(ALG_ID alg) { PROV_ENUMALGS alg_info; int alg_info_len, flag, found_alg; found_alg = 0; alg_info_len = sizeof(alg_info); flag = CRYPT_FIRST; while (CryptGetProvParam(base_prov, PP_ENUMALGS, (BYTE *)&alg_info, &alg_info_len, flag) && (!found_alg)) { if (alg_info.aiAlgid == alg) { found_alg = 1; } alg_info_len = sizeof(alg_info); flag = CRYPT_NEXT; } if (!found_alg) { if (GetLastError() != ERROR_NO_MORE_ITEMS) { mserror("CryptGetProvParam failed"); } return 0; } else { return 1; } } /** * Returns whether a particular cipher is supported */ int cipher_supported(int keytype) { ALG_ID alg; if ((alg = get_cipher(keytype)) == 0) { return 0; } return alg_found(alg); } /** * Returns whether a particular hash is supported */ int hash_supported(int hashtype) { ALG_ID alg; if ((alg = get_hash(hashtype)) == 0) { return 0; } return alg_found(alg); } /** * Gets the key length and IV/block length of a given key */ void get_key_info(int keytype, int *keylen, int *ivlen) { ALG_ID alg; int numkeys, i; alg = get_cipher(keytype); numkeys = sizeof(keyinfo) / sizeof(struct keyinfo_t); for (i = 0; i < numkeys; i++) { if (alg == keyinfo[i].alg) { *keylen = keyinfo[i].keysize; *ivlen = keyinfo[i].blocksize; return; } } *keylen = 0; *ivlen = 0; } /** * Gets the length of the given hash */ int get_hash_len(int hashtype) { ALG_ID alg; int numhash, i; alg = get_hash(hashtype); numhash = sizeof(hashinfo) / sizeof(struct hashinfo_t); for (i = 0; i < numhash; i++) { if (alg == hashinfo[i].alg) { return hashinfo[i].hashsize; } } return 0; } /** * Gets num cryptographically random bytes */ int get_random_bytes(unsigned char *buf, int num) { int rval; if (!(rval = CryptGenRandom(base_prov, num, buf))) { mserror("Error getting random bytes"); } return rval; } /** * Takes a block of data and encrypts it with a symmetric cypher. * The output buffer must be at least the size of source data + block size. */ int encrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { // TODO: right now we reimport the key each time. Test to see if this // is quick enough or if we need to cache an imported key. HCRYPTKEY hckey; char keyblob[BLOBLEN]; BLOBHEADER *bheader; DWORD *keysize; BYTE *keydata; int bloblen, keylen, ivlen, rval; ALG_ID alg; DWORD mode, _destlen; get_key_info(keytype, &keylen, &ivlen); alg = get_cipher(keytype); if (alg == 0) { log0(0, 0, "Invalid keytype"); return 0; } bheader = (BLOBHEADER *)keyblob; keysize = (DWORD *)(keyblob + sizeof(BLOBHEADER)); keydata = (BYTE *)((char *)keysize + sizeof(DWORD)); memset(keyblob, 0, sizeof(keyblob)); bheader->bType = PLAINTEXTKEYBLOB; bheader->bVersion = CUR_BLOB_VERSION; bheader->aiKeyAlg = alg; *keysize = keylen; memcpy(keydata, key, keylen); bloblen = sizeof(BLOBHEADER) + sizeof(DWORD) + keylen; if (!CryptImportKey(base_prov, keyblob, bloblen, 0, 0, &hckey)) { mserror("CryptImportKey failed"); return 0; } mode = CRYPT_MODE_CBC; if (!CryptSetKeyParam(hckey, KP_MODE, (BYTE *)&mode, 0)) { mserror("CryptSetKeyParam failed on KP_MODE"); rval = 0; goto end; } if (!CryptSetKeyParam(hckey, KP_IV, IV, 0)) { mserror("CryptSetKeyParam failed on KP_IV"); rval = 0; goto end; } memcpy(dest, src, srclen); _destlen = srclen; if (!CryptEncrypt(hckey, 0, 1, 0, dest, &_destlen, srclen + ivlen)) { mserror("CryptEncrypt failed"); rval = 0; goto end; } *destlen = _destlen; rval = 1; end: if (!CryptDestroyKey(hckey)) { mserror("CryptDestroyKey failed"); } return rval; } /** * Takes a block of data encrypted with a symmetric cypher and decrypts it. * The output buffer must be at least the size of source data. */ int decrypt_block(int keytype, const unsigned char *IV, const unsigned char *key, const unsigned char *aad, unsigned int aadlen, unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { // TODO: right now we reimport the key each time. Test to see if this // is quick enough or if we need to cache an imported key. HCRYPTKEY hckey; char keyblob[BLOBLEN]; BLOBHEADER *bheader; DWORD *keysize; BYTE *keydata; int bloblen, keylen, ivlen, rval; ALG_ID alg; DWORD mode, _destlen; get_key_info(keytype, &keylen, &ivlen); alg = get_cipher(keytype); if (alg == 0) { log0(0, 0, "Invalid keytype"); return 0; } bheader = (BLOBHEADER *)keyblob; keysize = (DWORD *)(keyblob + sizeof(BLOBHEADER)); keydata = (BYTE *)((char *)keysize + sizeof(DWORD)); memset(keyblob, 0, sizeof(keyblob)); bheader->bType = PLAINTEXTKEYBLOB; bheader->bVersion = CUR_BLOB_VERSION; bheader->aiKeyAlg = alg; *keysize = keylen; memcpy(keydata, key, keylen); bloblen = sizeof(BLOBHEADER) + sizeof(DWORD) + keylen; if (!CryptImportKey(base_prov, keyblob, bloblen, 0, 0, &hckey)) { mserror("CryptImportKey failed"); return 0; } mode = CRYPT_MODE_CBC; if (!CryptSetKeyParam(hckey, KP_MODE, (BYTE *)&mode, 0)) { mserror("CryptSetKeyParam failed on KP_MODE"); rval = 0; goto end; } if (!CryptSetKeyParam(hckey, KP_IV, IV, 0)) { mserror("CryptSetKeyParam failed on KP_IV"); rval = 0; goto end; } memcpy(dest, src, srclen); _destlen = srclen; if (!CryptDecrypt(hckey, 0, 1, 0, dest, &_destlen)) { mserror("CryptDecrypt failed"); rval = 0; goto end; } *destlen = _destlen; rval = 1; end: if (!CryptDestroyKey(hckey)) { mserror("CryptDestroyKey failed"); } return rval; } /** * Calculates the HMAC of the given message, hashtype, and hashkey. * dest must be at least the hash length. */ int create_hmac(int hashtype, const unsigned char *key, unsigned int keylen, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { // TODO: right now we reimport the hmac key each time. Test to see if this // is quick enough or if we need to cache an imported hmac key. HCRYPTKEY hmackey; HCRYPTHASH hash; char keyblob[BLOBLEN]; BLOBHEADER *bheader; DWORD *keysize; BYTE *keydata; HMAC_INFO info; ALG_ID alg; int bloblen, hashlen, rval; DWORD _destlen; hashlen = get_hash_len(hashtype); alg = get_hash(hashtype); if (alg == 0) { log0(0, 0, "Invalid hashtype"); return 0; } bheader = (BLOBHEADER *)keyblob; keysize = (DWORD *)(keyblob + sizeof(BLOBHEADER)); keydata = (BYTE *)((char *)keysize + sizeof(DWORD)); memset(keyblob, 0, sizeof(keyblob)); bheader->bType = PLAINTEXTKEYBLOB; bheader->bVersion = CUR_BLOB_VERSION; bheader->aiKeyAlg = CALG_RC2; *keysize = keylen; memcpy(keydata, key, keylen); bloblen = sizeof(BLOBHEADER) + sizeof(DWORD) + hashlen; if (!CryptImportKey(base_prov, keyblob, bloblen, 0, CRYPT_IPSEC_HMAC_KEY, &hmackey)) { mserror("CryptImportKey failed"); return 0; } if (!CryptCreateHash(base_prov, CALG_HMAC, hmackey, 0, &hash)) { mserror("CryptCreateHash failed"); rval = 0; goto end1; } memset(&info, 0, sizeof(info)); info.HashAlgid = alg; if (!CryptSetHashParam(hash, HP_HMAC_INFO, (BYTE *)&info, 0)) { mserror("CryptSetHashParam failed"); rval = 0; goto end2; } if (!CryptHashData(hash, src, srclen, 0)) { mserror("CryptHashData failed"); rval = 0; goto end2; } _destlen = hashlen; if (!CryptGetHashParam(hash, HP_HASHVAL, dest, &_destlen, 0)) { mserror("CryptGetHashParam failed"); rval = 0; goto end2; } *destlen = _destlen; rval = 1; end2: if (!CryptDestroyHash(hash)) { mserror("CryptDestroyHash failed"); } end1: if (!CryptDestroyKey(hmackey)) { mserror("CryptDestroyKey failed"); } return rval; } /** * Calculates the hash of the given message and hashtype */ int hash(int hashtype, const unsigned char *src, unsigned int srclen, unsigned char *dest, unsigned int *destlen) { HCRYPTHASH hash; ALG_ID alg; int hashlen, rval; DWORD _destlen; hashlen = get_hash_len(hashtype); alg = get_hash(hashtype); if (alg == 0) { log0(0, 0, "Invalid hashtype"); return 0; } if (!CryptCreateHash(base_prov, alg, 0, 0, &hash)) { mserror("CryptCreateHash failed"); return 0; } if (!CryptHashData(hash, src, srclen, 0)) { mserror("CryptHashData failed"); rval = 0; goto end; } _destlen = hashlen; if (!CryptGetHashParam(hash, HP_HASHVAL, dest, &_destlen, 0)) { mserror("CryptGetHashParam failed"); rval = 0; goto end; } *destlen = _destlen; rval = 1; end: if (!CryptDestroyHash(hash)) { mserror("CryptDestroyHash failed"); } return rval; } /** * Returns the length in bytes of the modulus for the given RSA key */ int RSA_keylen(const RSA_key_t rsa) { DWORD keylen, bsize; bsize = sizeof(keylen); if (!CryptGetKeyParam(rsa, KP_KEYLEN, (BYTE *)&keylen, &bsize, 0)) { mserror("CryptGetKeyParam failed"); return 0; } return keylen / 8; } int EC_keylen(const EC_key_t ec) { log0(0, 0, "EC not supported"); return 0; } int ECDSA_siglen(const EC_key_t ec) { log0(0, 0, "ECDSA not supported"); return 0; } /** * Encrypts a small block of data with an RSA public key. * Output buffer must be at least the key size. */ int RSA_encrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen) { DWORD _tolen; int flags; unsigned int i; unsigned char *outbuf; if (RSA_keylen(rsa) * 8 < 768) { flags = 0; } else { flags = CRYPT_OAEP; } outbuf = safe_calloc(RSA_keylen(rsa), 1); memcpy(outbuf, from, fromlen); _tolen = fromlen; if (!CryptEncrypt(rsa, 0, 1, flags, outbuf, &_tolen, RSA_keylen(rsa))) { mserror("CryptEncrypt failed"); free(outbuf); return 0; } *tolen = _tolen; // CryptoAPI returns ciphertext in little endian, so reverse the bytes for (i = 0; i < _tolen; i++) { to[i] = outbuf[_tolen - i - 1]; } free(outbuf); return 1; } /** * Decrypts a small block of data with an RSA private key. */ int RSA_decrypt(RSA_key_t rsa, const unsigned char *from, unsigned int fromlen, unsigned char *to, unsigned int *tolen) { DWORD _tolen; int flags; unsigned int i; if (RSA_keylen(rsa) * 8 < 768) { flags = 0; } else { flags = CRYPT_OAEP; } // CryptoAPI expects ciphertext in little endian, so reverse the bytes for (i = 0; i < fromlen; i++) { to[i] = from[fromlen - i - 1]; } _tolen = fromlen; if (!CryptDecrypt(rsa, 0, 1, flags, to, &_tolen)) { mserror("CryptDecrypt failed"); return 0; } *tolen = _tolen; return 1; } /** * Hashes a block of data and signs it with an RSA private key. * Output buffer must be at least the key size. */ int create_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen) { HCRYPTHASH hash; DWORD _siglen; int idx, found; ALG_ID alg; int hashlen, rval; unsigned int i; unsigned char *outsig; for (idx = 0, found = 0; (idx < MAXLIST) && (!found); idx++) { if (private_key_list[idx].key == rsa) { found = 1; } } if (!found) { log0(0, 0, "Couldn't find provider for RSA key"); return 0; } idx--; hashlen = get_hash_len(hashtype); alg = get_hash(hashtype); if (alg == 0) { log0(0, 0, "Invalid hashtype"); return 0; } if (!CryptCreateHash(private_key_list[idx].provider, alg, 0, 0, &hash)) { mserror("CryptCreateHash failed"); return 0; } if (!CryptHashData(hash, mes, meslen, 0)) { mserror("CryptHashData failed"); rval = 0; goto end; } _siglen = RSA_keylen(rsa); outsig = safe_calloc(_siglen, 1); if (!CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, outsig, &_siglen)) { mserror("CryptSignHash failed"); free(outsig); rval = 0; goto end; } *siglen = _siglen; // CryptoAPI returns signatures in little endian, so reverse the bytes for (i = 0; i < _siglen; i++) { sig[i] = outsig[_siglen - i - 1]; } free(outsig); rval = 1; end: if (!CryptDestroyHash(hash)) { mserror("CryptDestroyHash failed"); } return rval; } /** * Hashes a block of data and verifies it against an RSA signature. */ int verify_RSA_sig(RSA_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int siglen) { HCRYPTHASH hash; ALG_ID alg; unsigned hashlen, i; int rval; unsigned char *insig; hashlen = get_hash_len(hashtype); alg = get_hash(hashtype); if (alg == 0) { log0(0, 0, "Invalid hashtype"); return 0; } if (!CryptCreateHash(base_prov, alg, 0, 0, &hash)) { mserror("CryptCreateHash failed"); return 0; } if (!CryptHashData(hash, mes, meslen, 0)) { mserror("CryptHashData failed"); rval = 0; goto end; } insig = safe_calloc(siglen, 1); // CryptoAPI expects signatures in little endian, so reverse the bytes for (i = 0; i < siglen; i++) { insig[i] = sig[siglen - i - 1]; } if (!CryptVerifySignature(hash, insig, siglen, rsa, NULL, 0)) { mserror("CryptVerifySignature failed"); free(insig); rval = 0; goto end; } free(insig); rval = 1; end: if (!CryptDestroyHash(hash)) { mserror("CryptDestroyHash failed"); } return rval; } int create_ECDSA_sig(EC_key_t rsa, int hashtype, const unsigned char *mes, unsigned int meslen, unsigned char *sig, unsigned int *siglen) { log0(0, 0, "ECDSA not supported"); return 0; } int verify_ECDSA_sig(EC_key_t ec, int hashtype, const unsigned char *mes, unsigned int meslen, const unsigned char *sig, unsigned int siglen) { log0(0, 0, "ECDSA not supported"); return 0; } int get_ECDH_key(EC_key_t pubkey, EC_key_t privkey, unsigned char *key, unsigned int *keylen) { log0(0, 0, "ECDH not supported"); return 0; } /** * Creates an RSA public key with the given modulus and public exponent */ int import_RSA_key(RSA_key_t *rsa, const unsigned char *keyblob, uint16_t bloblen) { struct rsa_blob_t *rsablob; const unsigned char *modulus; char ms_keyblob[BLOBLEN]; BLOBHEADER *bheader; RSAPUBKEY *pubkeyheader; BYTE *ms_blob_modulus; int ms_bloblen, modlen, i; rsablob = (struct rsa_blob_t *)keyblob; modulus = keyblob + sizeof(struct rsa_blob_t); modlen = ntohs(rsablob->modlen); if (sizeof(struct rsa_blob_t) + modlen != bloblen) { log0(0, 0, "Error importing RSA key: invalid length"); return 0; } bheader = (BLOBHEADER *)ms_keyblob; pubkeyheader = (RSAPUBKEY *)(ms_keyblob + sizeof(BLOBHEADER)); ms_blob_modulus = (BYTE *)pubkeyheader + sizeof(RSAPUBKEY); memset(ms_keyblob, 0, sizeof(ms_keyblob)); bheader->bType = PUBLICKEYBLOB; bheader->bVersion = CUR_BLOB_VERSION; bheader->aiKeyAlg = CALG_RSA_KEYX; pubkeyheader->magic = 0x31415352; pubkeyheader->bitlen = modlen * 8; pubkeyheader->pubexp = ntohl(rsablob->exponent); // CrypoAPI expects the modulus in little endian, so reverse the bytes for (i = 0; i < modlen; i++) { ms_blob_modulus[i] = modulus[modlen - i - 1]; } ms_bloblen = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + modlen; if (!CryptImportKey(base_prov, ms_keyblob, ms_bloblen, 0, 0, rsa)) { mserror("CryptImportKey failed"); return 0; } return 1; } /** * Extracts the modulus and public exponent from an RSA public key */ int export_RSA_key(const RSA_key_t rsa, unsigned char *keyblob, uint16_t *bloblen) { struct rsa_blob_t *rsablob; unsigned char *modulus; char ms_keyblob[BLOBLEN]; BLOBHEADER *bheader; RSAPUBKEY *pubkeyheader; BYTE *ms_blob_modulus; int ms_bloblen, i; uint16_t modlen; rsablob = (struct rsa_blob_t *)keyblob; modulus = keyblob + sizeof(struct rsa_blob_t); ms_bloblen = sizeof(ms_keyblob); if (!CryptExportKey(rsa, 0, PUBLICKEYBLOB, 0, ms_keyblob, &ms_bloblen)) { mserror("CryptExportKey failed"); return 0; } bheader = (BLOBHEADER *)ms_keyblob; pubkeyheader = (RSAPUBKEY *)(ms_keyblob + sizeof(BLOBHEADER)); ms_blob_modulus = (BYTE *)pubkeyheader + sizeof(RSAPUBKEY); modlen = (pubkeyheader->bitlen / 8) & 0xFFFF; rsablob->blobtype = KEYBLOB_RSA; rsablob->reserved = 0; rsablob->modlen = htons(modlen); rsablob->exponent = htonl(pubkeyheader->pubexp); // CrypoAPI exports the modulus in little endian, so reverse the bytes for (i = 0; i < modlen; i++) { modulus[i] = ms_blob_modulus[modlen - i - 1]; } *bloblen = sizeof(struct rsa_blob_t) + modlen; return 1; } int import_EC_key(EC_key_t *ec, const unsigned char *keyblob, uint16_t bloblen, int isdh) { log0(0, 0, "EC keys not supported"); return 0; } int export_EC_key(const EC_key_t ec, unsigned char *keyblob, uint16_t *bloblen) { log0(0, 0, "EC keys not supported"); return 0; } /** * Generates an RSA private key with the given exponent and number of bits * and writes it into the specified key container */ RSA_key_t gen_RSA_key(int bits, int exponent, const char *container) { int idx, found, flags; const char *_container; // First find available private key slot for (idx = 0, found = 0; (idx < MAXLIST) && (!found); idx++) { if (private_key_list[idx].provider == 0) { found = 1; } } if (!found) { log0(0, 0, "Couldn't find empty key slot for private key"); return 0; } idx--; if (!container || !strcmp(container, "")) { _container = NULL; flags = machine_keyset | CRYPT_VERIFYCONTEXT; } else { _container = container; flags = machine_keyset; } if (!CryptAcquireContext(&private_key_list[idx].provider, _container, NULL, prov_type, flags)) { if (!CryptAcquireContext(&private_key_list[idx].provider, _container, NULL, prov_type, CRYPT_NEWKEYSET | flags)) { mserror("CryptAcquireContext failed"); return 0; } } if (!CryptGenKey(private_key_list[idx].provider, AT_KEYEXCHANGE, ((bits ? bits : DEF_RSA_LEN) << 16) | CRYPT_EXPORTABLE, &private_key_list[idx].key)) { mserror("CryptGenKey failed"); return 0; } return private_key_list[idx].key; } /** * Loads an RSA private key from the specified key container */ RSA_key_t read_RSA_key(const char *container) { int idx, found; // First find available private key slot for (idx = 0, found = 0; (idx < MAXLIST) && (!found); idx++) { if (private_key_list[idx].provider == 0) { found = 1; } } if (!found) { log0(0, 0, "Couldn't find empty key slot for private key"); return 0; } idx--; if (!CryptAcquireContext(&private_key_list[idx].provider, container, NULL, prov_type, machine_keyset)) { mserror("CryptAcquireContext failed"); return 0; } if (!CryptGetUserKey(private_key_list[idx].provider, AT_KEYEXCHANGE, &private_key_list[idx].key)) { mserror("CryptGetUserKey failed"); return 0; } return private_key_list[idx].key; } EC_key_t gen_EC_key(uint8_t curve, int isdh, const char *filename) { log0(0, 0, "EC keys not supported"); return NULL; } EC_key_t read_EC_key(const char *filename) { log0(0, 0, "EC keys not supported"); return NULL; } union key_t read_private_key(const char *filename, int *keytype) { union key_t key; key.rsa = read_RSA_key(filename); if (key.rsa == 0) { *keytype = 0; } else { *keytype = KEYBLOB_RSA; } return key; } uint8_t get_EC_curve(const EC_key_t ec) { log0(0, 0, "EC keys not supported"); return 0; } void free_RSA_key(RSA_key_t rsa) { if (!CryptDestroyKey(rsa)) { mserror("CryptDestroyKey failed"); } } void free_EC_key(EC_key_t ec) { log0(0, 0, "EC keys not supported"); } void set_sys_keys(int set_sys_key) { if (set_sys_key) { machine_keyset = CRYPT_MACHINE_KEYSET; } else { machine_keyset = 0; } } uftp-4.1.5/protocol.txt0000644000076400007640000025215312176075261014124 0ustar dbushdbush UFTP - Encrypted UDP based FTP with multicast 1. Introduction A UFTP session consists of 3 main phases: The Announce/Register phase, the File Transfer phase, and the Completion/Confirmation phase. The File Transfer phase additionally consists of the File Info phase and the Data Transfer phase for each file sent. The Announce/Register phase sets up the multicast file transfer session and negotiates all encryption parameters. The server sends out an announcement over a public multicast address which the clients are expected to be listening on. All subsequent messages from the server go over a private multicast address specified in the announcement. Allowed clients send a registration to respond to the announcement. The server will then send either a confirmation message if encryption is disabled, or the encryption keys for the session if encryption is enabled. If the client receives the encryption keys, it sends an acknowledgment back to the server. The File Transfer phase starts with the File Info phase for the first file to send. The server sends a message describing the file in question. Besides the name and size of the file, this message describes how the file will be broken down. A file is divided into a number of blocks, and these blocks are grouped into sections. A block is a piece of the file that is sent in a single packet, and a section is a grouping of blocks. The total number of blocks and sections is included in this message. Continuing the File Transfer phase is the Data Transfer phase for the first file. Data packets, each of which is a block, are sent by the server at a rate specified by the user. Because UDP does not guarantee that packets will arrive in order, each block is numbered so the client can properly reassemble the file. When the server has finished sending all data packets, it sends a message to the clients indicating this. When a client detects the end of a section or receives an end of file message from the server, and the client has detected one or more missing blocks, the client will send back a message containing a list of NAKs (negative acknowledgments). When the server receives NAKs from one or more clients, it goes back and retransmits any blocks that were NAKed, then continues on sending any untransmitted blocks. When a client has received the entire file, it sends a completion message in response to the server's end of file message. This continues until all clients have either send a completion message or have timed out after the server sent its end of file message. The File Info phase and the Data Transfer phase are then repeated for each file to be sent during the session. The Completion/Confirmation phase shuts down the session between the server and clients. It starts with a message from the server indication the end of the session. The clients then respond with a completion message, and the server responds to each completion with a confirmation message. 2. Definitions Server: A process run on demand that sends one or more files. Client: Daemon process that receives files. Public multicast address: A multicast address that a client or proxy expects to receive announcements from a server on. Private multicast address: Specified by the server when a session is initiated. All traffic from the server other than announcements are sent to this address. Proxy: Daemon process that relays UFTP traffic between servers and clients. Server proxy: A proxy, typically local to a server, that forms the upstream end of a multicast tunnel. It listens on the public multicast address (and private multicast address when specified) and forwards downstream packets to a specific address downstream. Upstream packets are forwarded back where the announcement originated from. Client proxy: A proxy, typically local to one or more clients, that forms the downstream end of a multicast tunnel. It receives unicast data from one or more server proxies and forwards downstream traffic to the multicast address specified in the packet header. Upstream traffic from clients is gathered and forwarded back where the announcement came from as an aggregated response. Response proxy: A proxy that functions as a response aggregator in situations where the server has direct multicast accessibility to clients but the number of clients are to high for the server to handle itself. It listens on the public multicast address (and private multicast address when specified), but does not forward packets from the server since those packets reach clients directly. It does however send some messages directly to clients in the process of establishing encryption keys. Upstream traffic from clients is gathered and forwarded back where the announcement came from as an aggregated response. Clients in this environment are configured to send all responses to a specific response proxy. Messages sent directly from response proxies to clients use multicast (either the primary public address, or the private address, depending on the message). Block: A piece of a file to be sent in a single packet. Section: A grouping of blocks. A section of blocks is the most the server can send before requesting status from a client. A client can send back a single status message to represent the NAKs for an entire section. 3. Timing 3.1 Round Trip Timing The server measures the round trip time (RTT) to each of the clients in order to determine the proper retransmission timing. In an ANNOUNCE, the server includes a timestamp. This timestamp is echoed back in the REGISTER sent by each client, adjusted by the time between when the client received the ANNOUNCE and when it sent the REGISTER. The server then takes the difference between the time in the REGISTER and the current time as the RTT to that client. The server keeps track of the group round trip time (GRTT) which is advertised to the clients in each message sent by the server. On the initial announce, this is set to a resonable default. Once RTTs for the clients have been measured, the largest of the individual RTTs becomes the GRTT. After the GRTT has been set once based on the RTTs of one or more clients, any subsequent adjustments have a floor of 0.9 times the prior value (RFC 5401). 3.2 Message Timing The GRTT is used to determine the timing of message retransmissions in both directions. A message robustness factor (ROBUST) is used as part of the timing calculations. This value is also sent to the clients in an ANNOUCE so that both server and clients will use a common value. The server will wait 3 * GRTT between each group of ANNOUNCE, REGCONF, KEYINFO, and FILEINFO messages sent before sending the next set. Clients should normally respond in the (1 * GRTT -- 2 * GRTT) time window. An extra GRTT is added to this to allow for a proxy to aggregate client messages and pass them on. Once all clients have responded during a given phase, the server will wait 1 * GRTT for any late responses before ending the phase. During the Announce/Register and File Info phases, the server will send the relevant messages a maximum of ROBUST times, after which clients that have not responsed will be marked as dropped. When a client detects that a new section has started, or upon receiving a DONE, it start a timer lasting 1 * GRTT. When this timer triggers, the client will send a STATUS for each section containing a NAK up to the last section completed, or up to the section specified in a DONE. When the server receives a STATUS message from a client, it first checks if the section listed in the STATUS is prior to the current transmission section. If not, it is ignored, otherwise the NAKs are recorded for retransmission. The server will then (at the end of the current pass of data, and if it hasn't done so already) start a timer lasting 3 * GRTT. When this timer expires, the server will rewind its transmission position to the earliest recorded NAK. When the server reaches the end of data transmission, it will send DONE messages every 3 * GRTT up to a maximum of ROBUST times to prompt clients to send a COMPLETE or STATUS. This continues until all clients respond with a COMPLETE, at least one client responds with a STATUS in which case it will set the rewind timer as above, or ROBUST DONEs are sent in which case any client that didn't respond with a DONE is marked as lost. TODO: Enter a holdoff period like NORM and add later NAKs? After a client sends a REGISTER, it will be retransmitted every 4 * GRTT with a maximum time of ROBUST * GRTT, after which the client drops the session. During the Completion/Confirmation stage, the client will retransmit a COMPLETE every 4 * GRTT with a maximum of ROBUST * GRTT, after which the client considers the session complete and does the normal cleanup. During the Data Transfer phase, if there are no packets received in ROBUST * GRTT, the client drops the session. In all cases, the maximum time is no less than one second. When a proxy receives a REGISTER, FILEINFO_ACK, STATUS, or COMPLETE from a client, it will wait for 1 * GRTT for any related messages before sending an aggregated response to the server. The proxy can also send a KEYINFO to one or more clients. It will wait 2 * GRTT for responses before resending. When a proxy has no pending aggregated messages to send, it will time out the session if no packets have been received in ROBUST * GRTT. When sending an aggregated REGISTER or FILEINFO_ACK message, a proxy will note which client reported the longest RTT based on the echoed timestamp and will include that client's timestamp in the reponse. That client will also be listed first in the body of the message so the server may determine which individual client has the longest RTT. TODO: Figure out if we want the server to send one message per cycle or one group of messages per cycle. The former will help manage message implosion but will take longer, while the later is quicker but more prone to message implosion, and is the way version 3.x does it. 3.3 Congestion Control A congestion control scheme based on TFMCC (RFC 4654) will be employed. To comply with this, each data packet includes a sequence number (cc_seq) and the current congestion control rate (cc_rate). A separate CONG_CTRL message will also be sent once per GRTT. This message also contains cc_seq and cc_rate along with a timestamp for RTT measurements and a list of receivers along with the RTT and a set of flags for each. Receivers send back CC_ACK messages periodically in accordance with RFC 4654. The TFMCC specific info in this message is included as a header extension. This extension is also included in STATUS messages. When a proxy prepares to send an aggregated STATUS message, it determines the current limiting receiver (CLR) among the clients that contributed to the aggregated message and forwards that client's congestion control info along with the STATUS message. Proxies will not aggregate CC_ACK messages and will forward them on immediately, adjusting the echoed timestamp as necessary. 4. Encryption Setup The encryption protocol borrows from TLS (RFC5246) and DTLS (RFC4347). Due to the large amount of traffic that may be generated while communicating with a large number of clients, the required key exchange messages are made as small as possible and integrated into the UFTP messaging structure to minimize the amount of traffic in the setup phase. Valid block encryption algorithms are DES and 3 key Triple DES in CBC mode, and AES-128 and AES-256 in either CBC, GCM or CCM mode. While AES is more secure and therefore preferred, DES and Triple DES are supported for environments where AES is not available. GCM and CCM modes are for authenticated encryption, so a separate signature is not required. For the block encryption algorithm chosen, there are: encryption_key_length IV_length For each message to be encrypted, an IV must be generated as follows: For a 128-bit IV (for AES in CBC mode): IV = S + UID + CTR For a 96-bit IV (for AES in GCM or CCM mode): IV = (S XOR UID) + CTR For a 64-bit IV (for DES or 3 key Triple DES): IV = (S + UID) XOR CTR where CTR is a monotinically increasing 64-bit counter, UID is the unique ID of the message sender, and S is a 32-bit salt, derived below. Valid hash algorithms are SHA-1, SHA-256, SHA-384, and SHA-512. While SHA-256 or more is preferred, SHA-1 is supported for environments where SHA-256 is not available. For the hash algorithm chosen, there is: hash_length The server has the option to use a symmetric key HMAC, an asymmetric key signature (RSA or ECDSA), or a cipher in GCM or CCM mode for authentication of encrypted messages. Using a GCM or CCM cipher allows encryption and signature generation to be done in a single step and is fastest (particularly GCM). HMAC is slower that GCM/CCM but faster than asymmetric keys. While asymmetric key signatures are larger and slower, they do protect against attacks from within the group. When using asymmetric key signatures, all clients MUST use a key of the same type and size (for RSA) or curve (for ECDSA) as the server. Clients may maintain multiple private keys of differing types and sizes to accommodate different servers. During the initial setup, there is an exchange of symmetric keys between the server and each client. This can be done with either RSA encryption or with an Elliptic Curve Diffie-Hellman (ECDH) key exchange. With ECDH, the key exchange parameters are validated with either RSA signatures or Elliptic Curve DSA (ECDSA) signatures. In the ANNOUNCE message, the server sends a random number along with certain key exchange keys. The random number is 32 bytes, with the first 4 being the current time in the standard UNIX time format, and the remaining 28 generated by a secure random number generator. If RSA is chosen for key exchange, the ANNOUNCE includes the server's public RSA key. If ECDH is chosen for key exchange, it contains an ephemeral EC public key to use for the ECDH exchange along with either its RSA public key or its ECDSA public key, plus a signature over the whole UDP packet using the RSA/ECDSA key. Each client then sends in the REGISTER its own random number, constructed identically to the server's, along with it's part of the key exchange. In the case of RSA key exchange, it includes a 48 byte premaster secret key encrypted with the server's public key. The premaster secret consists of 1 byte for the client's version and the remaining 47 chosen by a secure random number generator. In the case of an ECDH key exchange, it includes an ephemeral EC public key, using the same EC curve as the server's ECDH key, for the ECDH key exchange. The server and client will then perform an ECDH key derivation, with the result hashed with SHA-1, and the resulting value becomes the premaster secret. The client may also send a CLIENT_KEY message along with a REGISTER. This message is required if the server requests either RSA/ECDSA signatures or simply to authenticate the client. This messages also contains a signature (using the client's RSA or ECDSA key) of the hash of: group ID + private address + server random + client random + premaster secret The server and each client calculates a 48 byte master secret as follows: master_secret = PRF(pre_master_secret, "master secret", server_random + client_random)[0-47] When SHA-1 is the selected hash, the pseudo-random function (PRF) is as defined in TLS 1.1 (RFC4346) which uses a SHA1/MD5 combination. Otherwise, the pseudo-random function (PRF) is as defined in TLS 1.2 (RFC5246) using the server selected hash function. Using the master secret, a key block is generated: key_block = PRF(master_secret, "key expansion", server_random + client_random); From the key block, the first hash_length bytes comprise the HMAC key, the next encryption_key_length bytes comprise the session key, and the next 4 bytes comprise the salt from which session IVs are derived. Currently, the HMAC key based on the master secret between the server and a particular client is not used, but the key material is generated anyway to be consistent with key expansion of the group key and as a hedge in case it could be used in a future release. After receiving the REGISTER the server sends a KEYINFO message containing the group master key encrypted with the session key for the client, along with a 64-bit counter used to generate the IV. If an authenticated cipher mode was chosen (GCM/CCM), the encryption is instead performed in CBC mode as a signature is not requried at this stage. A KEYINFO can contain the group key encrypted multiple times, with each one encrypted with a different client's session key and grouped with the unique ID of the client. The group master key is chosen by the server. It is 48 bytes, with the first byte being the server version and the remaining 47 bytes chosen by a secure random number generator. To save bandwidth, only the 47 random bytes are encrypted. Given the block size of the supported cyphers of 8 or 16, this results in the encrypted key being 48 bytes (instead of 64, if 48 bytes were encrypted). The other byte can be derived from the version of the KEYINFO message. From the group master key, a key block is generated as follows: key_block = PRF(group_master, "key expansion", server_random); From the key block, the first hash_length bytes comprise the group HMAC key, the next encryption_key_length bytes comprise the group session key, and the next 4 bytes comprise the salt from which group session IVs are derived. Upon receiving the KEYINFO message, the client responds with an KEYINFO_ACK message. This message contains a 12 byte verify_data field constructed as follows: PRF(group_master, "client finished", Hash(handshake_values))[0-11] Where Hash is the server selected hash function and handshake_values is: group ID + private address + server random + client random + premaster secret + [ client_key_blob ] + group_master where client_key_blob is an exported keyblob of the client's public key, if a CLIENT_KEY message was sent. See section 5 for details of this keyblob. All subsequent messages after a KEYINFO from either client or server are encrypted using the group session key, and the whole UDP packet is signed with either the group HMAC key, the sender's RSA/ECDSA key, or the GCM/CCM mode cipher, depending on what the server selects. Each of these messages also contains a 64-bit counter used to generate the IV for that message. The unique ID used to generate the IV should be the same one placed in the Source ID field. Clients receiving a KEYINFO message also uses the unique ID from Source ID. A server proxy may choose to dynamically determine its destination client proxy. It does this by means of authenticated heartbeat messages. The server proxy specifies the public key fingerprint of the client proxy it wishes to send to. This is useful when the client proxy is NATed and its IP address cannot be determined ahead of time, and may in fact change on the fly. When a server proxy in this mode receives an HB_REQ message, it responds with an HB_RESP message containing a flag indicating that authentication is required along with a randomly chosen nonce value. The client proxy is then expected to sign the nonce with its public key and sends back the nonce, the signed nonce, and its public key in another HB_REQ message. When the server proxy receives this message, it first checks the plaintext nonce to see if it matches the last nonce it sent out. If so, it verifies the client proxy's public key fingerprint then verifies the signature. If these checks all pass, the server uses the incoming address of the HB_REQ as its downstream address, and sends back an HB_RESP indicating successful authentication. At this time the server will randomly select a new nonce so the old one is not reused. If any of the checks fail, the server proxy sends back an HB_RESP message indicating failed authentication, and the current destination remains unchanged. Any future HB_REQ messages the server proxy gets from the established downstream client proxy is automatically accepted. Only when the IP/port does not match will the server proxy issue a challenge. 5. Messages 5.1 Message Flow While it would save one round trip to send a KEYINFO and FILEINFO together with encryption enabled and only one file to send, keeping them separate prevents eavesdroppers from determining information about the encrypted stream, such as the number of files being sent. See section 3.2 for more details. 5.1.1 Announce/Register phase with encryption: 5.1.1.1 Without proxies Server Client ------ ------ ANNOUNCE ---> <--- REGISTER <--- CLIENT_KEY (optional) KEYINFO ---> <--- KEYINFO_ACK 5.1.1.2 With server/client proxies Server Server Proxy Client Proxy Client ------ ------------ ------------ ------ ANNOUNCE ---> ---> ---> <--- REGISTER <--- CLIENT_KEY (optional) <--- <--- REGISTER <--- <--- CLIENT_KEY (optional) KEYINFO ---> ---> <--- <--- KEYINFO_ACK REG_CONF ---> ---> KEYINFO ---> <--- KEYINFO_ACK 5.1.1.3 With response proxy Server Response Proxy Client ------ -------------- ------ ANNOUNCE ---> ------------------------> <--- REGISTER <--- CLIENT_KEY (optional) <--- REGISTER <--- CLIENT_KEY (optional) KEYINFO ---> <--- KEYINFO_ACK REG_CONF ---> KEYINFO ---> <--- KEYINFO_ACK 5.1.2 Announce/Register phase without encryption 5.1.2.1 Without proxies Server Client ------ ------ ANNOUNCE ---> <--- REGISTER REG_CONF ---> 5.1.2.2 With server/client proxies Server Server Proxy Client Proxy Client ------ ------------ ------------ ------ ANNOUNCE ---> ---> ---> <--- REGISTER <--- <--- REGISTER REG_CONF ---> ---> ---> 5.1.2.3 With response proxy Server Response Proxy Client ------ -------------- ------ ANNOUNCE ---> ------------------------> <--- REGISTER <--- REGISTER REG_CONF ---> ------------------------> 5.1.3 File Transfer phase: Server Client ------ ------ FILEINFO ---> (either) <--- FILEINFO_ACK (or) <--- COMPLETE FILESEG ---> ... CONG_CTRL ---> <--- CC_ACK ... DONE ---> (either) <--- STATUS (or) <--- COMPLETE ... 5.1.4 Completion/Confirmation phase: Server Client ------ ------ DONE ---> <--- COMPLETE DONE_CONF ---> 5.2 Message Details All messages start with the UFTP header, followed by a message specific header. With the exception of an ENCRYPTED message, each message header starts with an 8 bit function number. This allows for determining the message type of a decrypted message, so the message type is not revealed in the UFTP header. Messages may contain optional header extensions. Some are defined here, although others may be added without breaking backward compatibility. Any unexpected header extension should be ignored. 5.2.1 UFTP header 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | UFTP ID | Function | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Group ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Group Inst. | GRTT | Group Size | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ UFTP ID: 8 bits Defines the version number of the protocol. Currently 0x30. Function: 8 bits The message number of the contained message. If the message is encrypted, this always specifies the message number for ENCRYPTED. Sequence Number: 16 bits A monotonically increasing sequence number used for congestion control and replay attack purposes. Source ID: 32 bits The unique ID of the sender. Group ID: 32 bits A unique identifier for the current session. Group Instance: 8 bits A sequence number that increases each time the session identified by the Group ID is retried. This allows for differentiating retried instances of the same session. GRTT: 8 bits For messages originating from a server, a quantized version of the current Group Round Trip Time. See RFC 5401 section 3.7.4 for details of how this field is encoded. Group Size: 8 bits For messages originating from a server, a quantized version of the total number of clients in the current session. This field consists of a 5 bit mantissa and a 3 bit exponent. The mantissa is scaled so that a value of 0.0 is represented as 0 and a value of 10.0 is represented as 32. Reserved: 8 bits Reserved for future use and should be set to 0. 5.2.2 ENCRYPTED 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IV Counter (high bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IV Counter (low bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Signature length | Payload Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Signature | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Encrypted Payload | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Specifies an encrypted message. Contains a signature followed by the encrypted message payload (which is one of the messages below). The signature is either an HMAC using the group key or an RSA/ECDSA signature using the sender's RSA/ECDSA key over the whole UFTP message, depending on the signature type chosen by the server. Alternately, if an authenticated cipher was chosen, the signature field will be empty since the signature is embedded in the encrypted data. IV Counter: 64 bits A monotonically increasing counter used as part of the symmetric key IV for the encrypted payload. This field should be implemented as two 32-bits fields for purposes of alignment. Signature length: 16 bits The length of the signature in bytes (must be a multiple of 4). Payload length: 16 bits The length of the encrypted payload in bytes (must be a multiple of 4). Signature: varies The signature for this message. It applies to the entire UFTP packet, including the UFTP header. May be either an HMAC using the group hmac key or an RSA/ECDSA signature using the sender's RSA/ECDSA private key. Encrypted Payload: varies The encrypted message, encrypted with the group symmetric key. 5.2.3 ANNOUNCE 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Flags | Robust Factor | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | CC Type | Reserved | Block Size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_seconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_microseconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Multicast Address | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Private Multicast Address | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the server to initiate a file transfer. Contains basic info needed by clients to initiate a session. For closed group membership, the list of allowed clients is specified in the body. Multiple messages may be sent to accommodate the full list of clients. This message goes over the public multicast address. All subsequent server messages go over the private multicast address. If the server needs to resend this message under closed group membership, only clients that did not respond are listed. Function: 8 bits The message number for this message. Always 1. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Flags: 8 bits 0x01 - SYNC_MODE If set, this indicates that the session is operating in sync mode. Files received by clients are checked against existing files to see whether or not the file should be accepted. 0x02 - SYNC_PREVIEW Indicates sync preview mode for the session. This is like sync mode, except client don't actually receive any files. 0x04 - IPV6 Indicates that the public multicast address and private multicast address are IPv6 addresses when set. All other bits should be set to 0. Robust: 8 bits Sets the robustness factor for the session. This value dictates how many times certain messages may be retransmitted. CC Type: 8 bits Specifies the congestion control algorithm in use. See section 5.3 for a list of valid values. Reserved: 8 bits Reserved for future use and should be set to 0. Block Size: 16 bits The size of the body for UFTP messages. This value should be somewhat less that the path MTU to allow room for any UFTP header or extension as well as the UDP and IP headers and extensions. Timestamp_seconds: 32 bits The current time expressed as seconds since the UNIX epoch of 1/1/1970 00:00:00 UTC. Timestamp_microseconds: 32 bits The microsecond portion of the current time. This field and the above field are used in round trip time measurements. Public Mulicast Address: varies The public multicast address used by this session. May be an IPv4 address of 4 bytes or an IPv6 address of 16 bytes. Private Mulicast Address: varies The private multicast address used by this session. May be an IPv4 address of 4 bytes or an IPv6 address of 16 bytes. The public and private addresses must be the same IP version. Header Extensions: varies Contains any header extensions that may be present Client ID: 32 bits each The body of the message. Contains the unique ID of one or more clients that are allowed to join under closed group membership. 5.2.3.1 EXT_ENC_INFO 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension | Extension | Flags | Keyex | Sig | | Type | Length | | Type | Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Key Type | Hash Type | Key Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Diffie-Hellman Key Length | Signature Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + + | | + + | Server Random Number | + + | | + + | | + + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Blob | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Diffie-Hellman Key Blob | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Public Key Signature | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ A header extension appended to an ANNOUNCE message indicating encryption parameters for the session. Extention Type: 8 bits The extension number for this extension. Always 1. Extension Length: 8 bits The total length of the extension in 4 byte words. Flags: 8 bits 0x01 - CLIENT_AUTH Specifies that the client should send a CLIENT_KEY message in additionto a REGISTER when responding when set. All other bits should be set to 0. Key Exchange Type: 4 bits Specifies the key exchange algorithm to use. See section 5.3 for the list of valid values. Signature Type: 4 bits Specifies the signature type number of the signature to use on encrypted messages. See section 5.3 for the list of valid values. Key Type: 8 bits Specifies the key type number of the symmetric encryption algorithm to use. See section 5.3 for the list of valid values. Hash Type: 8 bits Specifies the hash type number of the hashing algorithm to use for HMAC signatures and key derivation. See section 5.3 for the list of valid values. Public Key Length: 16 bits The length in bytes of the server's public key blob. Diffie-Hellman Key Length: 16 bits The length in bytes of the server's Diffie-Hellman key blob. Must be 0 if the key exchange algorithm is RSA. Signature Length: 16 bits The length in bytes of the server's public key signature. Must be 0 if the key exchange algorithm is RSA. Server Random Number: 256 bits A 32-byte random number chosen by the server used to derive the master secret key between the server and each client. Public Key Blob: varies A key blob containing the server's RSA or ECDSA public key. Diffie-Hellman Key Blob: varies A key blob containing the server's ephemeral ECDH public key. Public Key Signature: varies The signature for this message. It applies to the entire UFTP packet, including the UFTP header. Signed using the sender's RSA/ECDSA private key. 5.2.3.2 RSA key blob 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Blob Type | Reserved | Modulus Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Exponent | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Modulus | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ A keyblob containing an RSA public key Blob Type: 8 bits Specifies the keyblob type. Always 1. Reserved: 8 bits MUST be set to 0. Modulus Length: 16 bits The length in bytes of the RSA public key modulus Public Key Exponent: 32 bits The public key exponent of the RSA public key. Public Key Modulus: varies The public key modulus of the RSA public key. 5.2.3.3 EC key blob 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Blob Type | Curve | Key Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | EC Public Key | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ A keyblob containing an Elliptic Curve public key, which may be used for either ECDH or ECDSA. Blob Type: 8 bits Specifies the keyblob type. Always 2. Curve: 8 bits Specifies the named curve used by this public key. See section 5.3 for the list of valid values. Key Length: 16 bits The Length in bytes of the the EC public key. EC Public Key: varies The EC public key value, specified as the X coordinate followed by the Y coordinate. 5.2.4 REGISTER 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Key Info Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_seconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_microseconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + + | | + + | Client Random Number | + + | | + + | | + + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Key Info | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the client to acknowledge receipt of an ANNOUNCE. If encryption is requested, also contains a random number and a premaster secret key encrypted with the server's public RSA key. Function: 8 bits The message number for this message. Always 2. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Key Info Length: 16 bits The length in bytes of the key info field. Timestamp_seconds: 32 bits The seconds part of the last server timestamp sent, adjusted for the time between receiving the last server message and sending this message. Timestamp_microseconds: 32 bits The microseconds part of the last server timestamp sent, adjusted as above. Client Random Number: 256 bits A 32-byte random number chosen by the client used to derive the master secret key between the server and each client. Key Info: varies Either the premaster secret key selected by the client, encrypted with the server's RSA public key, or the client's ephemeral ECDH public key. Client ID: 32 bits each The body of the message. Contains the unique ID of one or more clients that a proxy received a REGSITER from and is now relaying to the server. 5.2.5 CLIENT_KEY 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Length | Signed Verify Data Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Blob | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Signed Verify Data | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Sent by the client if the server requests client authentication, or if the server requests RSA/ECDSA signatures instead of HMAC. Function: 8 bits The message number for this message. Always 3. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. Public Key Length: 16 bits The length in bytes of the client's public key blob. Signed Verify Data Length: 16 bits The length in bytes of the signed verify data field. Public Key Blob: varies A keyblob containing client's RSA or ECDSA public key. Signed Verify Data: varies The signature from the client's RSA/ECDSA private key of verification data based on the hash of the cryptographic parameters exchanged to this point. 5.2.6 REG_CONF 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the server in response to a REGISTER if there is no encryption. Contains a list of clients that the server received a REGISTER for, and multiple messages may be sent to accommodate the full list of clients. Also sent if the REGISTER came from a proxy. This allows proxies to confirm registrations when encryption is enabled. The server will not resend this message for a given client unless it receives an extra REGSITER from that client. Function: 8 bits The message number for this message. Always 4. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. Client ID: 32 bits each The body of the message. Contains the unique ID of one or more clients that the server received a REGISTER for. 5.2.7 KEYINFO 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IV Counter (high bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IV Counter (low bits) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ encrypted | Client ID | key 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + + | | + + | | + + | | + + | Encrypted Group Master | + + | | + + | | + + | | + + | | + + | | + + | | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ encrypted | Client ID | key 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the server in response to a REGISTER if encryption is enabled. Contains the list of clients that the server received a REGISTER for, and multiple message may be sent to accommodate the full list of clients. If the server needs to resend this message, only clients that did not respond are listed. Also, for each listed client, it contains the group master key encrypted with the master secret key negotiated with that client. If a REGISTER is received from a proxy, KEYINFO gets sent directly to the proxy, not the clients it serves. Function: 8 bits The message number for this message. Always 5. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. IV Counter: 64 bits A monotonically increasing counter used as part of the symmetric key IV for the encrypted group master key for each listed client. This field should be implemented as two 32-bits fields for purposes of alignment. The message body contains a list of the following pair of fields: Client ID: 32 bits each The unique ID of a client that the server received a REGISTER for. Encrypted Group Master: 384 bits each The last 47 bytes of the 48 byte group master secret, encrypted using the master symmetric key for the assiciated client. 5.2.8 KEYINFO_ACK 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | Verify Data | + + | | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the client in response to a KEYINFO. Contains verification data based on the hash of the cryptographic parameters exchanged to this point. This message is always encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 6. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. Verify Data: 96 bits Contains a hash of all cryptographic parameters exchanged for the session 5.2.9 FILEINFO 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | File ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | File Type | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Name Length | Link Length | File Size (high bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | File Size (low bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | File Timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_seconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_microseconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | File Name | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Link Name | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the server to give information on a particular file being sent. Contains the list of currently active clients, and multiple message may be sent to accommodate the full list of clients. If the server needs to resend this message, only clients that did not respond are listed. If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 7. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. File ID: 16 bits The identifier of the current file. Chosen by the server sequentially starting at 1. File Type: 8 bits Specifies the type of file being sent (regular file, directory, sybolic link, delete request, or free dist space request). See section 4.3 for the list of valid values. Reserved: 24 bits Reserved for future use and should be set to 0. Name Length: 8 bits The length of the file name in 4 byte words. If the file name is not a multiple of 4, this field is null padded at the end. Link Length: 8 bits The length of the link name in 4 byte words. If the link name is not a multiple of 4, this field is null padded at the end. May be 0. File Size: 48 bits The size of the file to send in bytes. File Timestamp: 32 bits Specifies the file's timestamp as seconds since the UNIX epoch of 1/1/1970 00:00:00 UTC. Timestamp_seconds: 32 bits The current time expressed as seconds since the UNIX epoch of 1/1/1970 00:00:00 UTC. Timestamp_microseconds: 32 bits The microsecond portion of the current time. This field and the above field are used in round trip time measurements. File Name: varies The path name of the file to send. A slash (/) is used as a directory separator. The file gets created with this path in the client's destination directory. Link Name: varies If the File Type field specifies that the file is a symbolic link, this field specifies the link destination. Client ID: 32 bits each The body of the message. Contains the ID of one or more active clients. 5.2.10 FILEINFO_ACK 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | File ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Flags | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_seconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_microseconds | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the client in response to a FILEINFO. Contains the file ID . If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 8. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. File ID: 16 bits The identifier of the current file. Flags: 8 bits 0x01 - PARTIAL If set, this indicates that the client partially received the indicated file on a prior run. Valid only in response to a FILEINFO. All other bits should be set to 0. Reserved: 24 bits Reserved for future use and should be set to 0. Timestamp_seconds: 32 bits The seconds part of the last server timestamp sent, adjusted for the time between receiving the last server message and sending this message. Timestamp_microseconds: 32 bits The microseconds part of the last server timestamp sent, adjusted as above. Client ID: 32 bits each The body of the message. Contains the ID of one or more active clients. 5.2.11 FILESEG 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | File ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Section | Block Number in Section | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the server, and contains a block of data following this header. If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 9. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. File ID: 16 bits The identifier of the current file. Section: 16 bits The section number for this block. Block Number in Section: 16 bits The number of this block within the current section. Data: varies The body of the message. Contains a block of the current file. 5.2.11.1 EXT_TFMCC_DATA_INFO 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension | Extension | Send Rate | | Type | Length | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | CC Sequence | CC Rate | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ This extension, added to a FILESEG message, contains information necessary to support TFMCC. Extention Type: 8 bits The extension number for this extension. Always 2. Extension Length: 8 bits The total length of the extension in 4 byte words. Send Rate: 16 bits The current transmission rate of the server. CC Sequence: 16 bits A sequence number which increases by 1 at the start of a new feedback round. CC Rate: 16 bits The current congestion control suppression rate. Only clients with a rate less than this may send congestion control feedback in a CC_ACK message. 5.2.12 DONE 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | File ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Section | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the server at the end of a section to request NAKs. Contains the list of clients that the server needs status for, and multiple message may be sent to accommodate the full list of clients. If the server needs to resend this message, only clients that did not respond are listed. If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 10. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. File ID: 16 bits The identifier of the current file. If zero, indicates the beginning of the Completion/Confirmation phase to end the session. Section: 16 bits The section number the server is requesting status for. Reserved: 16 bits Reserved for future use and should be set to 0. Client ID: 32 bits each The body of the message. Contains the ID of one or more clients the server needs status for. 5.2.13 STATUS 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | File ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Section | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | NAK Content | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the client either in response to a DONE message or at the end of a section if loss is detected. If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 11. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. File ID: 16 bits The identifier of the current file. Section: 16 bits The section number this STATUS is requesting retransmissions for. Reserved: 16 bits Reserved for future use and should be set to 0. NAK Contest: varies The body of the message. Contains a bitmap of the blocks in the section, where a set bit designates a NAK for the given block. 5.2.13.1 EXT_TFMCC_ACK_INFO 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension | Extension | Flags | Reserved | | Type | Length | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | CC Sequence | CC Rate | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Client ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_seconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_microseconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ This extension, added to a STATUS or CC_ACK message, contains information necessary to support TFMCC. Extention Type: 8 bits The extension number for this extension. Always 3. Extension Length: 8 bits The total length of the extension in 4 byte words. Flags: 8 bits 0x01 - CC_CLR If set, specifies this client is the CLR. Not currently used. 0x02 - CC_RTT If set, specifies that the client has received an RTT measurement from the server. 0x04 - CC_START If set, specifies that the client is in slow start mode. 0x08 - CC_LEAVE If set, specifies that the client is preparing to leave the session. All other bits should be set to 0. Reserved: 8 bits Reserved for future use and should be set to 0. CC Sequence: 16 bits The most recent CC sequence number received from the server. CC Rate: 16 bits The client's congestion control rate. If CC_START is set, Client ID: The ID of the client that created this extension. When a proxy forwards or aggregates client responses, this allow the server to know who this extension belongs to. Timestamp_seconds: 32 bits The seconds part of the last server timestamp sent, adjusted for the time between receiving the last server message and sending this message. Timestamp_microseconds: 32 bits The microseconds part of the last server timestamp sent, adjusted as above. 5.2.14 COMPLETE 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | File ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Status | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client ID | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by the client in response to a DONE message when the client has received the whole file. May also be sent in a response to a FILEINFO if the session is a restart session and the client received the whole file on a prior attempt. If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. If the file ID is 0, indicating the end of the session, all files and directories sent during the session are moved from the the client's temp directory to the destination directory, if a temp directory was specified. Files within directories are moved as part of the containing directory. Function: 8 bits The message number for this message. Always 12. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. File ID: 16 bits The identifier of the current file. Status: 8 bits Specifies the status of the COMPLETE message. When in sync mode, a status of COMP_STAT_NORMAL specifies that the file was a new file copied over, a status of COMP_STAT_SKIPPED specifies that the file was skipped because the incoming file was older, and a status of COMP_STAT_OVERWRITE specifies that the file overwrote an existing file. when not in sync mode, the status is set to COMP_STAT_NORMAL if the file was sent successfully. If the client rejects the file due to a pathname or filename issue, regardless of sync mode, the status is set to COMP_STAT_REJECTED. Reserved: 24 bits Reserved for future use and should be set to 0. Client ID: 32 bits each The body of the message. Contains the ID of one or more clients that a proxy received a COMPLETE from and is now relaying to the server. 5.2.14.1 EXT_FREESPACE_INFO 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension | Extension | Reserved | | Type | Length | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Free Space (high bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Free Space (low bits) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ This extension, added to a COMPLETE message, contains the amount of free disk space in bytes in the client's primary destination directory. Extention Type: 8 bits The extension number for this extension. Always 7. Extension Length: 8 bits The total length of the extension in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. Free Space: 64 bits The amount of free disk space in bytes. 5.2.15 DONE_CONF 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Client IP Address | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Send by the server in response to a COMPLETE message at the end of a session. Contains the list of clients that have completed. Multiple messages may be sent to accommodate the full list of clients. The server will not resend this message for a given client unless it receives an extra COMPLETE from that client. If encryption is enabled, this message is encrypted and embedded within an ENCRYPTED message. Function: 8 bits The message number for this message. Always 13. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. Client ID: 32 bits each The body of the message. Contains the ID of one or more clients that a server received a COMPLETE from. 5.2.16 ABORT 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Flags | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Host | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Message | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Sent by either a client or server when an error condition occurs. This message may or may not be encrypted, depending on whether or not the group master key has been negotiated. Function: 8 bits The message number for this message. Always 19. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Flags: 8 bits 0x01 - CURRENT_FILE Applies only if sent to a client, and only if the Host field is zero. If set, specifies that all clients not actively working on the current file must abort. Clients that finished the current file do NOT abort and may receive the next file in the session. All other bits should be set to 0. Reserved: 8 bits Reserved for future use and should be set to 0. Host: 32 bits If sent by the server, specifies the client the server wishes to abort, or zero to specify that all clients must abort. If sent by a client, this is set to zero. If sent by a proxy on behalf of a client, it is set to the IP of the client that is aborting. If sent by a proxy on its own behalf, it is set to zero. Message: 300 bytes Descriptive text stating the reason for the abort. 5.2.17 HB_REQ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Length | Signed Nonce Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Nonce | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Blob | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Signed Nonce | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Sent by a proxy (usually a client proxy) to an upstream proxy for the purpose of opening up a hole in a firewall that the upstream proxy can send through, and revealing the proxy's NATed IP to the upstream proxy so the upstream proxy knows where to send other requests. Function: 8 bits The message number for this message. Always 14. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. Public Key Length: 16 bits The length in bytes of the proxy's public key blob. Signed Nonce Length: 16 bits The length in bytes of the signed nonce field. Nonce: 32 bits The value received from a previous HB_RESP that is to be signed. Public Key Blob: varies A keyblob containing the proxy's RSA or ECDSA public key. Signed Nonce: varies The signature from the proxy's RSA/ECDSA private key of the specified nonce. 5.2.18 HB_RESP 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Authenticated | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Nonce | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Sent by a proxy in response to an HB_REQ message. Function: 8 bits The message number for this message. Always 15. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Authenticated: 8 bits Specifies the status of the HB_REQ this message is responding to. A value of HB_AUTH_OK means authentication succeeded or was not required. A value of HB_AUTH_CHALLENGE means expected authentication info was not specified. A value of HB_AUTH_FAILED means the given authentication info was invalid. Reserved: 8 bits Reserved for future use and should be set to 0. Nonce: 32 bits When Authenticated = HB_AUTH_CHALLENGE, the nonce value that is expected to be signed in an authenticated HB_REQ. 5.2.19 KEY_REQ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Sent by a client to a response proxy to solicit a PROXY_KEY message. The client will send this message once every 5 seconds until it gets a valid response. Function: 8 bits The message number for this message. Always 16. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. 5.2.20 PROXY_KEY 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Public Key Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Diffie-Hellman Key Length | Signed Nonce Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Nonce | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Public Key Blob | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Diffie-Hellman Key Blob | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Signed Nonce | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Sent by a response proxy to clients for the purpose suppying its public RSA/ECDSA key and public ECDH key. When a client gets an ANNOUNCE directly from a server, it contains the server's RSA/ECDSA public key and perhaps an ECDH key. The client can then use the proxy's keys to perform the key exchange instead of the server's key. This message gets sent out on the first specified public multicast address so all downstream clients be read it. To avoid a denial of service attack, the proxy will not send this message more than once every 5 seconds. Function: 8 bits The message number for this message. Always 17. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Public Key Length: 16 bits The length in bytes of the proxy's public key blob. Diffie-Hellman Key Length: 16 bits The length in bytes of the proxy's public ECDH key blob. Signed Nonce Length: 16 bits The length in bytes of the signed nonce field. Nonce: 32 bits A randomly chosen value that will be signed by the proxy's RSA public key. Public Key Blob: varies A keyblob containing the proxy's RSA or ECDSA public key. Diffie-Hellman Key Blob: varies A keyblob containing the proxy's long-lived ECDH public key. Signed Nonce: varies The signature from the proxy's RSA/ECDSA private key of the specified nonce. 5.2.21 CONG_CTRL 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | CC Sequence | CC Rate | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_seconds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp_microseconds | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | CC Item | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ : .... : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Sent by a server as part of congestion control and round trip time calculations. This message supplies clients with an up to date timestamp to adjust and respond to for round trip time as well as a list of round trip times for one or more clients. Function: 8 bits The message number for this message. Always 20. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. CC Sequence: 16 bits A sequence number which increases by 1 at the start of a new feedback round. CC Rate: 16 bits The current congestion control suppression rate. Only clients with a rate less than this may send congestion control feedback in a CC_ACK message. Timestamp_seconds: 32 bits The current time expressed as seconds since the UNIX epoch of 1/1/1970 00:00:00 UTC. Timestamp_microseconds: 32 bits The microsecond portion of the current time. This field and the above field are used in round trip time measurements. CC Item: 64 bits each The body of the message. Contains a list of congestion control data items. Defined as follows. 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Client ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Flags | RTT | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Client ID: 32 bits The unique ID of the client this item represents. Flags: 8 bits 0x01 - CC_CLR If set, specifies this client is the CLR. 0x02 - CC_RTT If set, specifies that the server calculated the RTT to this client. 0x04 - CC_START If set, specifies that the server is in slow start mode. 0x08 - CC_LEAVE If set, specifies that the client is preparing to leave the session. Not currently used in this message. All other bits should be set to 0. RTT: 8 bits The round trip time to this client as calculated by the server. Reserved: 16 bits Reserved for future use and should be set to 0. 5.2.22 CC_ACK 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Function | Header Length | Reserved | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | Header Extensions (if applicable) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ This message contains a congestion control feedback response from a client. There should normaly be at least one header extension present such as EXT_TFMCC_ACK_INFO to supply the specific congestion control data for a particular scheme. Function: 8 bits The message number for this message. Always 21. Header Length: 8 bits The total length of the header, including extensions, in 4 byte words. Reserved: 16 bits Reserved for future use and should be set to 0. 5.3 Message Constants 5.3.1 Message type numbers ANNOUNCE 1 REGISTER 2 CLIENT_KEY 3 REG_CONF 4 KEYINFO 5 KEYINFO_ACK 6 FILEINFO 7 FILEINFO_ACK 8 FILESEG 9 DONE 10 STATUS 11 COMPLETE 12 DONE_CONF 13 HB_REQ 14 HB_RESP 15 KEY_REQ 16 PROXY_KEY 17 ENCRYPTED 18 ABORT 19 CONG_CTRL 20 CC_ACK 21 5.3.2 Key type numbers None 0 DES 1 Triple DES 2 AES 128 CBC 3 AES 256 CBC 4 AES 128 GCM 5 AES 256 GCM 6 AES 128 CCM 7 AES 256 CCM 8 5.3.3 Hash type numbers None 0 MD5 1 SHA-1 2 SHA-256 3 SHA-384 4 SHA-512 5 5.3.4 Signature type numbers None 0 HMAC 1 KEYEX 2 AUTHENC 3 5.3.5 Key Exchange type numbers None 0 RSA 1 ECDH_RSA 2 ECDH_ECDSA 3 5.3.6 Key Blob type numbers None 0 RSA 1 EC 2 5.3.7 Heartbeat authentication codes HB_AUTH_FAILED 0 HB_AUTH_OK 1 HB_AUTH_CHALLENGE 2 5.3.8 File types Regular file 0 Directory 1 Symbolic Link 2 Delete file 3 Get free space 4 5.3.9 Completion status COMP_STAT_NORMAL 0 COMP_STAT_SKIPPED 1 COMP_STAT_OVERWRITE 2 COMP_STAT_REJECTED 3 5.3.10 Extension types EXT_ENC_INFO 1 EXT_TFMCC_DATA_INFO 2 EXT_TFMCC_ACK_INFO 3 EXT_PGMCC_DATA_INFO 4 EXT_PGMCC_NAK_INFO 5 EXT_PGMCC_ACK_INFO 6 EXT_FREESPACE_INFO 7 5.3.11 Congestion control types CC_NONE 0 CC_UFTP3 1 CC_TFMCC 2 CC_PGMCC 3 5.3.12 Elliptic Curve named curves sect163k1 1 sect163r1 2 sect163r2 3 sect193r1 4 sect193r2 5 sect233k1 6 sect233r1 7 sect239k1 8 sect283k1 9 sect283r1 10 sect409k1 11 sect409r1 12 sect571k1 13 sect571r1 14 secp160k1 15 secp160r1 16 secp160r2 17 secp192k1 18 secp192r1 19 // also prime192v1 secp224k1 20 secp224r1 21 secp256k1 22 secp256r1 23 // also prime256v1 secp384r1 24 secp521r1 25 uftp-4.1.5/server_init.h0000644000076400007640000000270712250244021014203 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_INIT_H #define _SERVER_INIT_H void pre_initialize(void); void initialize(void); #endif // _SERVER_INIT_H uftp-4.1.5/client.h0000644000076400007640000002260112176066071013141 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_H #define _CLIENT_H #include "uftp_common.h" #include "encryption.h" #define MAXLIST 100 #define MAXMISORDER 5 #define KEY_REQ_INT 5 /** * Current state of client for a given group */ enum client_phase { PHASE_REGISTERED = 1, /// Registered and awaiting KEYINFO or REG_CONF PHASE_RECEIVING = 2, /// Currently receiving a file PHASE_COMPLETE = 3, /// Completed group and awaiting DONE_CONF PHASE_MIDGROUP = 4 /// Registered awaiting next file or group end }; /** * Info pertaining to current file */ struct file_t { uint32_t blocks; /// Total blocks uint16_t sections; /// Total sections uint16_t big_sections; /// Number of larger sized sections uint32_t secsize_small, secsize_big; /// Size of sections int ftype; /// File type (regular, directory, symlink) f_offset_t size; /// Size in bytes int32_t tstamp; /// File timestamp char filepath[MAXPATHNAME]; /// Local path to file char temppath[MAXPATHNAME]; /// Local path to temp file char name[MAXPATHNAME]; /// Path name char linkname[MAXPATHNAME]; /// Link name (symlinks only) uint8_t *naklist; /// NAK list uint8_t *section_done; /// Array of done flags for each section int fd; /// File descriptor for file uint32_t last_block; /// Block number of last block received uint16_t last_section; /// Section number of last block received struct timeval nak_time; /// Time to send out NAKs uint16_t nak_section_first; /// First section number to send NAKs for uint16_t nak_section_last; /// Last section number to send NAKs for int got_done; /// A DONE was received for this client f_offset_t curr_offset; /// Current file pointer offset in fd int restart; /// True if restarting a prior session int comp_status; /// Value for status field of COMPLETE int destdiridx; /// Index of dest dir file is received in }; /** * Header of client save state file. * Followed in the file by the NAK list and section_done list. * The naklist and section_done fields are left blank when the struct is * written to a file. When read back in, memory is allocated and the * NAK list and section_done list are written to them. */ struct client_restart_t { uint32_t blocks; /// Total blocks uint32_t sections; /// Total sections f_offset_t size; /// Size in bytes char name[MAXPATHNAME]; /// Path name uint8_t *naklist; /// NAK list uint8_t *section_done; /// Array of done flags for each section }; /** * Loss history item. * These are part of an array where the array index is the sequence number. */ struct loss_history_t { int found; /// True if this packet was received struct timeval t; /// Time received, either actual or inferred int size; /// Size of received packet, including UDP/IP }; /** * Loss event item. */ struct loss_event_t { uint32_t start_seq; /// Seq num of event start, including wraparound int len; /// Size of loss interval struct timeval t; /// Timestamp of event start }; /** * Info for a particular group */ struct group_list_t { uint32_t group_id; /// Group ID uint8_t group_inst; /// Group instance ID (restart number) uint16_t file_id; /// File ID of current file uint8_t version; /// Protocol version number of server union sockaddr_u multi; /// Private multicast address int multi_join; /// True if we're listening on private addr char start_date[10]; /// Date initial ANNOUNCE was received char start_time[10]; /// Time initial ANNOUNCE was received uint16_t send_seq; /// Outgoing seq. number for loss detection uint32_t src_id; /// ID of server (network byte order) union sockaddr_u replyaddr; int phase; /// Current client_phase of the group int client_auth, restart, sync_mode, sync_preview; /// Flags from ANNOUNCE struct client_restart_t *restartinfo; /// Restart file header unsigned int blocksize; /// Size of packet payload unsigned int datapacketsize; /// Max size of UFTP packet struct timeval timeout_time, start_timeout_time, expire_time; double rtt, grtt; /// Client's RTT and server's GRTT uint16_t start_txseq, max_txseq; /// Server's starting, max sequence # struct loss_history_t *loss_history; /// Loss history struct loss_event_t loss_events[9]; /// Loss event history int seq_wrap; /// Number of times server seq wrapped int ccseq; /// Current congestion control sequence # uint32_t initrate; /// Cong. control rate at start of fb round int isclr; /// True if this client is the CLR int slowstart; /// True if we're in slowstart mode uint8_t robust, cc_type; /// Robust factor, congestion control type uint32_t gsize; /// Group size estimate struct timeval cc_time; /// Timer for sending CC_ACK struct timeval last_server_ts, last_server_rx_ts; int keytype, hashtype, sigtype, keyextype; /// Encryption parameters union key_t server_pubkey; /// Server's public key union key_t client_privkey; /// Client's private key for this group union key_t server_dhkey; /// Server ECDH public key for this group union key_t client_dhkey; /// Client ECDH public key for this group unsigned int server_pubkeylen; /// Length in bytes of server key unsigned int client_privkeylen; /// Length in bytes of client key uint8_t rand1[RAND_LEN]; /// Server's random number uint8_t rand2[RAND_LEN]; /// Client's random number uint8_t premaster[MASTER_LEN]; /// Premaster secret sent by client unsigned int premaster_len; /// Length of premaster secret uint8_t master[MASTER_LEN]; /// Master key for client uint8_t hmackey[HMAC_LEN]; /// HMAC key for client uint8_t key[MAXKEY]; /// Symmetric encryption key for client uint8_t salt[MAXIV]; /// Salt for block cypher IV for client uint8_t groupmaster[MASTER_LEN];/// Master key for server uint8_t grouphmackey[HMAC_LEN]; /// HMAC key for server uint8_t groupkey[MAXKEY]; /// Symmetric encryption key for server uint8_t groupsalt[MAXIV]; /// Salt for block cypher IV for server uint64_t ivctr; /// Counter portion of the IV int ivlen, keylen, hmaclen; /// Length of HMAC key, symmetric key and iv struct file_t fileinfo; /// Info pertaining to current file }; /** * Global command line values and sockets */ extern SOCKET listener; extern char tempdir[MAXDIRNAME], destdir[MAXDIR][MAXDIRNAME]; extern char pidfile[MAXPATHNAME]; extern char keyfile[MAXLIST][MAXPATHNAME], keyinfo[MAXLIST][MAXPATHNAME]; extern char backupdir[MAXDIR][MAXDIRNAME]; extern int debug, encrypted_only, dscp, destdircnt, tempfile, keyinfo_count; extern int interface_count, pub_multi_count, keyfile_count, rcvbuf, backupcnt; extern char postreceive[MAXPATHNAME], portname[PORTNAME_LEN]; extern int port, move_individual; extern uint32_t uid; extern union sockaddr_u hb_hosts[MAXLIST]; extern struct iflist m_interface[MAX_INTERFACES]; extern union sockaddr_u pub_multi[MAX_INTERFACES]; extern struct group_list_t group_list[MAXLIST]; extern struct fp_list_t server_keys[MAXLIST]; extern struct iflist ifl[MAX_INTERFACES]; extern struct timeval next_keyreq_time, next_hb_time; extern int ifl_len, server_count, key_count, has_proxy, sys_keys, priority; extern int hbhost_count, hb_interval; extern union key_t privkey[MAXLIST]; extern int privkey_type[MAXLIST]; extern struct fp_list_t proxy_info; extern union key_t proxy_pubkey, proxy_dhkey; extern int proxy_pubkeytype; #endif // _CLIENT_H uftp-4.1.5/makefile0000664000076400007640000001676312250244021013212 0ustar dbushdbush# # UFTP - UDP based FTP with multicast # # Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, the copyright holder # grants you additional permission to convey the resulting work. # Corresponding Source for a non-source form of such a combination # shall include the source code for the parts of OpenSSL used as well # as that of the covered work. # UNAME_S:=$(shell uname -s) ifdef NO_ENCRYPTION ENC_OPTS=-DNO_ENCRYPTION CRYPT_LIB= else ifdef NO_EC ENC_OPTS=-DNO_EC CRYPT_LIB=-lcrypto else ENC_OPTS= CRYPT_LIB=-lcrypto endif # defaults CC = gcc OPTIONS=-g -Wall $(ENC_OPTS) LDLIBS=-lc -lm $(CRYPT_LIB) CFLAGS= MTFLAGS= # FreeBSD ifeq ("FreeBSD", "$(UNAME_S)") OPTIONS=-g -Wall -DHAS_GETIFADDRS -DNO_DUAL -DNO_MCAST_JOIN $(ENC_OPTS) endif # OSX, aka Darwin ifeq ("Darwin", "$(UNAME_S)") OPTIONS=-g -Wall -DHAS_GETIFADDRS $(ENC_OPTS) endif # Sun ifeq ("SunOS", "$(UNAME_S)") CC = cc OPTIONS=-g -DBSD_COMP -DNO_DUAL $(ENC_OPTS) LDLIBS=-lnsl -lsocket -lm $(CRYPT_LIB) CFLAGS=`getconf LFS_CFLAGS` OPENSSL=/usr/sfw MTFLAGS=-mt endif # Linux ifeq ("Linux", "$(UNAME_S)") OPTIONS=-g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wformat=2 -Wwrite-strings -Wpointer-arith -Wcast-qual -Wshadow -Wno-missing-field-initializers -Wstrict-prototypes -Winline -Wbad-function-cast -DHAS_GETIFADDRS $(ENC_OPTS) LDLIBS=-lm $(CRYPT_LIB) CFLAGS=`getconf LFS_CFLAGS` endif ifdef OPENSSL INCLUDE=-I $(OPENSSL)/include LIB=-L $(OPENSSL)/lib endif all: uftp uftpd uftpproxyd uftp_keymgt test: testclient_multi testclient_multi.o: testclient_multi.c uftp.h uftp_common.h encryption.h testclient_multi: testclient_multi.o encrypt_openssl.o uftp_common.o $(CC) $(OPTIONS) $(LIB) -o $@ $^ $(LDLIBS) clean: rm -f testclient_multi uftp uftpd uftpproxyd uftp_keymgt *.o client_announce.o: client_announce.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_announce.h client_common.o: client_common.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_config.o: client_config.c client.h uftp_common.h uftp.h \ encryption.h client_config.h client_fileinfo.o: client_fileinfo.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_fileinfo.h client_transfer.h client_init.o: client_init.c client.h uftp_common.h uftp.h encryption.h \ client_init.h client_config.h client_common.h client_loop.o: client_loop.c client.h uftp_common.h uftp.h encryption.h \ client_common.h client_loop.h client_announce.h client_fileinfo.h \ client_transfer.h heartbeat_send.h client_main.o: client_main.c client_config.h client_init.h client_loop.h client_transfer.o: client_transfer.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_transfer.h server_announce.o: server_announce.c server.h uftp_common.h uftp.h \ encryption.h server_common.h server_announce.h server_common.o: server_common.c server.h uftp_common.h uftp.h \ encryption.h server_common.h server_config.o: server_config.c server.h uftp_common.h uftp.h \ encryption.h server_config.h server_init.o: server_init.c server.h uftp_common.h uftp.h encryption.h \ server_config.h server_init.h server_main.o: server_main.c server_config.h server_init.h server_send.h server_phase.o: server_phase.c server.h uftp_common.h uftp.h encryption.h \ server_config.h server_common.h server_announce.h server_transfer.h server_send.o: server_send.c server.h uftp_common.h uftp.h encryption.h \ server_send.h server_phase.h server_common.h server_transfer.o: server_transfer.c server.h uftp_common.h uftp.h \ encryption.h server_common.h server_transfer.h proxy_common.o: proxy_common.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_common.h proxy_upstream.h proxy_config.o: proxy_config.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_config.h proxy_downstream.o: proxy_downstream.c proxy.h uftp_common.h uftp.h \ encryption.h proxy_common.h proxy_downstream.h proxy_init.o: proxy_init.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_init.h proxy_config.h proxy_common.h proxy_loop.o: proxy_loop.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_common.h proxy_loop.h proxy_upstream.h proxy_downstream.h \ heartbeat_send.h proxy_main.o: proxy_main.c proxy_config.h proxy_init.h proxy_loop.h proxy_upstream.o: proxy_upstream.c proxy.h uftp_common.h uftp.h \ encryption.h proxy_common.h proxy_upstream.h proxy_downstream.h uftp_keymgt.o: uftp_keymgt.c uftp_common.h uftp.h encryption.h encrypt_openssl.o: encrypt_openssl.c uftp_common.h uftp.h encryption.h encrypt_none.o: encrypt_none.c encryption.h uftp_common.o: uftp_common.c uftp.h uftp_common.h encryption.h heartbeat_send.o: heartbeat_send.c uftp_common.h uftp.h encryption.h \ heartbeat_send.h ifdef NO_ENCRYPTION UFTP_OBJS=uftp_common.o encrypt_none.o \ server_announce.o server_transfer.o server_send.o server_phase.o \ server_common.o server_config.o server_init.o server_main.o UFTPD_OBJS=uftp_common.o encrypt_none.o \ client_loop.o client_announce.o client_fileinfo.o client_transfer.o \ client_common.o client_config.o client_init.o client_main.o heartbeat_send.o UFTPPROXYD_OBJS=uftp_common.o encrypt_none.o \ proxy_loop.o proxy_upstream.o proxy_downstream.o \ proxy_common.o proxy_config.o proxy_init.o proxy_main.o heartbeat_send.o UFTP_KEYMGT_OBJS=uftp_keymgt.o uftp_common.o encrypt_none.o else UFTP_OBJS=uftp_common.o encrypt_openssl.o \ server_announce.o server_transfer.o server_send.o server_phase.o \ server_common.o server_config.o server_init.o server_main.o UFTPD_OBJS=uftp_common.o encrypt_openssl.o \ client_loop.o client_announce.o client_fileinfo.o client_transfer.o \ client_common.o client_config.o client_init.o client_main.o heartbeat_send.o UFTPPROXYD_OBJS=uftp_common.o encrypt_openssl.o \ proxy_loop.o proxy_upstream.o proxy_downstream.o \ proxy_common.o proxy_config.o proxy_init.o proxy_main.o heartbeat_send.o UFTP_KEYMGT_OBJS=uftp_keymgt.o uftp_common.o encrypt_openssl.o endif uftp: $(UFTP_OBJS) $(CC) $(OPTIONS) $(LIB) -o $@ $^ $(LDLIBS) $(MTFLAGS) -lpthread uftpd: $(UFTPD_OBJS) $(CC) $(OPTIONS) $(LIB) -o $@ $^ $(LDLIBS) uftpproxyd: $(UFTPPROXYD_OBJS) $(CC) $(OPTIONS) $(LIB) -o $@ $^ $(LDLIBS) uftp_keymgt: $(UFTP_KEYMGT_OBJS) $(CC) $(OPTIONS) $(LIB) -o $@ $^ $(LDLIBS) %.o: %.c $(CC) $(OPTIONS) $(INCLUDE) $(CFLAGS) $(MTFLAGS) -c $< install: all install -m 755 -d $(DESTDIR)/bin install -m 755 -d $(DESTDIR)/usr/sbin install -m 755 -d $(DESTDIR)/usr/share/man/man1 /bin/cp -p uftp $(DESTDIR)/bin /bin/cp -p uftpd $(DESTDIR)/usr/sbin /bin/cp -p uftpproxyd $(DESTDIR)/usr/sbin /bin/cp -p uftp_keymgt $(DESTDIR)/bin /bin/cp -p uftp.1 uftpd.1 uftpproxyd.1 uftp_keymgt.1 $(DESTDIR)/usr/share/man/man1 uftp-4.1.5/server_announce.h0000644000076400007640000000540612137013343015052 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2012 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _SERVER_ANNOUNCE_H #define _SERVER_ANNOUNCE_H #include "server.h" int send_announce(const struct finfo_t *finfo, int attempt, int open); int send_regconf(const struct finfo_t *finfo, int attempt, int do_regconf); int send_keyinfo(const struct finfo_t *finfo, int attempt); int send_fileinfo(const struct finfo_t *finfo, int attempt); void handle_open_register(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, uint32_t src, int regconf); void handle_register(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, int hostidx, int regconf, int open); void handle_open_clientkey(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, uint32_t src); void handle_clientkey(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, int hostidx); void handle_keyinfo_ack(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, const union sockaddr_u *su, int hostidx); void handle_fileinfo_ack(const unsigned char *message, unsigned meslen, struct finfo_t *finfo, int hostidx); #endif // _SERVER_ANNOUNCE_H uftp-4.1.5/makefile.mak0000644000076400007640000002123112137013343013746 0ustar dbushdbush# # UFTP - UDP based FTP with multicast # # Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, the copyright holder # grants you additional permission to convey the resulting work. # Corresponding Source for a non-source form of such a combination # shall include the source code for the parts of OpenSSL used as well # as that of the covered work. # CFLAGS=-Zi -W3 -nologo -Od /D WINDOWS !IFDEF NO_ENCRYPTION CFLAGS=$(CFLAGS) /D NO_ENCRYPTION LDLIBS = ws2_32.lib iphlpapi.lib !ELSE IFDEF OPENSSL CFLAGS=$(CFLAGS) /D OPENSSL /MD LDLIBS = libeay32.lib ssleay32.lib ws2_32.lib iphlpapi.lib INCLUDE = $(INCLUDE);C:\OpenSSL\include LIB = $(LIB);C:\OpenSSL\lib !ELSE IFDEF WINXP LDLIBS = advapi32.lib ws2_32.lib iphlpapi.lib !ELSE LDLIBS = bcrypt.lib ncrypt.lib ws2_32.lib iphlpapi.lib !ENDIF !IFDEF WINXP CFLAGS=$(CFLAGS) /D _WIN32_WINNT=_WIN32_WINNT_WINXP !ELSE CFLAGS=$(CFLAGS) /D _WIN32_WINNT=_WIN32_WINNT_WIN7 !ENDIF LDFLAGS=-debug -nologo -subsystem:console CC = cl LINK = link all: uftp.exe uftpd.exe uftpproxyd.exe uftp_keymgt.exe clean: -del /f uftp.exe uftpd.exe uftpproxyd.exe uftp_keymgt.exe *.obj *.pdb *.ilk test: testclient_multi.exe testclient_multi.obj: testclient_multi.c uftp.h uftp_common.h encryption.h !IFDEF OPENSSL testclient_multi.exe: testclient_multi.obj encrypt_openssl.obj uftp_common.obj win_func.obj $(LINK) $(LDFLAGS) -out:$@ $** $(LDLIBS) !ELSE testclient_multi.exe: testclient_multi.obj encrypt_cryptoapi.obj uftp_common.obj win_func.obj $(LINK) $(LDFLAGS) -out:$@ $** $(LDLIBS) !ENDIF client_announce.obj: client_announce.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_announce.h client_common.obj: client_common.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_config.obj: client_config.c client.h uftp_common.h uftp.h \ encryption.h client_config.h client_init.obj: client_init.c client.h uftp_common.h uftp.h encryption.h \ client_init.h client_config.h client_common.h client_loop.obj: client_loop.c client.h uftp_common.h uftp.h encryption.h \ client_common.h client_loop.h client_announce.h \ client_transfer.h heartbeat_send.h client_main.obj: client_main.c client_config.h client_init.h client_loop.h client_transfer.obj: client_transfer.c client.h uftp_common.h uftp.h \ encryption.h client_common.h client_transfer.h server_announce.obj: server_announce.c server.h uftp_common.h uftp.h \ encryption.h server_common.h server_announce.h server_common.obj: server_common.c server.h uftp_common.h uftp.h \ encryption.h server_common.h server_config.obj: server_config.c server.h uftp_common.h uftp.h \ encryption.h server_config.h server_init.obj: server_init.c server.h uftp_common.h uftp.h encryption.h \ server_config.h server_init.h server_main.obj: server_main.c server_config.h uftp.h server_init.h \ server_send.h server_phase.obj: server_phase.c server.h uftp_common.h uftp.h encryption.h \ server_common.h server_announce.h server_transfer.h server_send.obj: server_send.c server.h uftp_common.h uftp.h encryption.h \ server_send.h server_phase.h server_transfer.obj: server_transfer.c server.h uftp_common.h uftp.h \ encryption.h server_common.h server_transfer.h proxy_common.obj: proxy_common.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_common.h proxy_upstream.h proxy_config.obj: proxy_config.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_config.h proxy_downstream.obj: proxy_downstream.c proxy.h uftp_common.h uftp.h \ encryption.h proxy_common.h proxy_downstream.h proxy_init.obj: proxy_init.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_init.h proxy_config.h proxy_common.h proxy_loop.obj: proxy_loop.c proxy.h uftp_common.h uftp.h encryption.h \ proxy_common.h proxy_loop.h proxy_upstream.h proxy_downstream.h \ heartbeat_send.h proxy_main.obj: proxy_main.c proxy_config.h proxy_init.h proxy_loop.h proxy_upstream.obj: proxy_upstream.c proxy.h uftp_common.h uftp.h \ encryption.h proxy_common.h proxy_upstream.h proxy_downstream.h uftp_keymgt.obj: uftp_keymgt.c uftp_common.h uftp.h encryption.h encrypt_cng.obj: encrypt_cng.c uftp_common.h uftp.h encryption.h encrypt_cryptoapi.obj: encrypt_cryptoapi.c uftp_common.h uftp.h encryption.h encrypt_openssl.obj: encrypt_openssl.c uftp_common.h uftp.h encryption.h uftp_common.obj: uftp_common.c uftp.h uftp_common.h encryption.h heartbeat_send.o: heartbeat_send.c uftp_common.h uftp.h encryption.h \ heartbeat_send.h win_func.obj: win_func.c win_func.h !IFDEF NO_ENCRYPTION UFTP_OBJS=win_func.obj uftp_common.obj encrypt_none.obj \ server_announce.obj server_transfer.obj \ server_send.obj server_phase.obj \ server_common.obj server_config.obj server_init.obj server_main.obj UFTPD_OBJS=win_func.obj uftp_common.obj encrypt_none.obj \ client_loop.obj client_announce.obj client_transfer.obj \ client_common.obj client_config.obj client_init.obj client_main.obj \ heartbeat_send.obj UFTPPROXYD_OBJS=win_func.obj uftp_common.obj encrypt_none.obj \ proxy_loop.obj proxy_upstream.obj proxy_downstream.obj \ proxy_common.obj proxy_config.obj proxy_init.obj proxy_main.obj \ heartbeat_send.obj UFTP_KEYMGT_OBJS=uftp_keymgt.obj win_func.obj uftp_common.obj encrypt_none.obj !ELSE IFDEF OPENSSL UFTP_OBJS=win_func.obj uftp_common.obj encrypt_openssl.obj \ server_announce.obj server_transfer.obj \ server_send.obj server_phase.obj \ server_common.obj server_config.obj server_init.obj server_main.obj UFTPD_OBJS=win_func.obj uftp_common.obj encrypt_openssl.obj \ client_loop.obj client_announce.obj client_transfer.obj \ client_common.obj client_config.obj client_init.obj client_main.obj \ heartbeat_send.obj UFTPPROXYD_OBJS=win_func.obj uftp_common.obj encrypt_openssl.obj \ proxy_loop.obj proxy_upstream.obj proxy_downstream.obj \ proxy_common.obj proxy_config.obj proxy_init.obj proxy_main.obj \ heartbeat_send.obj UFTP_KEYMGT_OBJS=uftp_keymgt.obj win_func.obj uftp_common.obj encrypt_openssl.obj !ELSE IFDEF WINXP UFTP_OBJS=win_func.obj uftp_common.obj encrypt_cryptoapi.obj \ server_announce.obj server_transfer.obj \ server_send.obj server_phase.obj \ server_common.obj server_config.obj server_init.obj server_main.obj \ UFTPD_OBJS=win_func.obj uftp_common.obj encrypt_cryptoapi.obj \ client_loop.obj client_announce.obj client_transfer.obj \ client_common.obj client_config.obj client_init.obj client_main.obj \ heartbeat_send.obj UFTPPROXYD_OBJS=win_func.obj uftp_common.obj encrypt_cryptoapi.obj \ proxy_loop.obj proxy_upstream.obj proxy_downstream.obj \ proxy_common.obj proxy_config.obj proxy_init.obj proxy_main.obj \ heartbeat_send.obj UFTP_KEYMGT_OBJS=uftp_keymgt.obj win_func.obj uftp_common.obj encrypt_cryptoapi.obj !ELSE UFTP_OBJS=win_func.obj uftp_common.obj encrypt_cng.obj \ server_announce.obj server_transfer.obj \ server_send.obj server_phase.obj \ server_common.obj server_config.obj server_init.obj server_main.obj \ UFTPD_OBJS=win_func.obj uftp_common.obj encrypt_cng.obj \ client_loop.obj client_announce.obj client_transfer.obj \ client_common.obj client_config.obj client_init.obj client_main.obj \ heartbeat_send.obj UFTPPROXYD_OBJS=win_func.obj uftp_common.obj encrypt_cng.obj \ proxy_loop.obj proxy_upstream.obj proxy_downstream.obj \ proxy_common.obj proxy_config.obj proxy_init.obj proxy_main.obj \ heartbeat_send.obj UFTP_KEYMGT_OBJS=uftp_keymgt.obj win_func.obj uftp_common.obj encrypt_cng.obj !ENDIF uftp.exe: $(UFTP_OBJS) $(LINK) $(LDFLAGS) -out:$@ $** $(LDLIBS) uftpd.exe: $(UFTPD_OBJS) $(LINK) $(LDFLAGS) -out:$@ $** $(LDLIBS) uftpproxyd.exe: $(UFTPPROXYD_OBJS) $(LINK) $(LDFLAGS) -out:$@ $** $(LDLIBS) uftp_keymgt.exe: $(UFTP_KEYMGT_OBJS) $(LINK) $(LDFLAGS) -out:$@ $** $(LDLIBS) .c.obj: $(CC) -c $(CFLAGS) $< uftp: uftp.exe uftpd: uftpd.exe uftpproxyd: uftpproxyd.exe uftp_keymgt: uftp_keymgt.exe testclient_multi: testclient_multi.exe uftp-4.1.5/uftp_keymgt.c0000644000076400007640000001223712176066071014220 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #ifdef WINDOWS #include "win_func.h" #else // if WINDOWS #include #endif #include "uftp_common.h" #include "encryption.h" void print_key(const char *name) { union key_t key; int keytype; key = read_private_key(name, &keytype); if (keytype == KEYBLOB_RSA) { fprintf(stderr, "%s: RSA, %d bits, fingerprint: %s\n", name, RSA_keylen(key.rsa) * 8, print_key_fingerprint(key, KEYBLOB_RSA)); free_RSA_key(key.rsa); } else if (keytype == KEYBLOB_EC) { fprintf(stderr, "%s: ECDSA, curve %s, fingerprint: %s\n", name, curve_name(get_EC_curve(key.ec)), print_key_fingerprint(key, KEYBLOB_EC)); free_EC_key(key.ec); } else { fprintf(stderr, "%s: no such key\n", name); } } int main(int argc, char *argv[]) { union key_t key; int i, c; int gen_key_len, del_key, sys_key; uint8_t gen_key_curve; const char opts[] = "g:dm"; log_level = 2; max_log_size = 0; init_log_mux = 0; applog = stderr; gen_key_len = 0; gen_key_curve = 0; del_key = 0; sys_key = 0; while ((c = getopt(argc, argv, opts)) != EOF) { switch (c) { case 'g': if (!strncmp("ec:", optarg, 3)) { gen_key_curve = get_curve(&optarg[3]); if (gen_key_curve == 0) { fprintf(stderr, "Invalid curve"); exit(1); } } else if (!strncmp("rsa:", optarg, 4)) { gen_key_len = atoi(&optarg[4]); if ((gen_key_len < 512) || (gen_key_len > 2048)) { fprintf(stderr, "Invalid key size\n"); exit(1); } } else { fprintf(stderr, "Invalid key specification\n"); exit(1); } break; case 'd': del_key = 1; break; case 'm': sys_key = 1; break; } } argc -= optind; argv += optind; if (((gen_key_len != 0) || gen_key_curve != 0) && (del_key != 0)) { fprintf(stderr, "Can't specify both -g and -d\n"); exit(1); } crypto_init(sys_key); key.key = 0; if (gen_key_len) { if (argc < 1) { fprintf(stderr, "No keyfile specified\n"); exit(1); } key.rsa = gen_RSA_key(gen_key_len, RSA_EXP, argv[0]); if (key.key) { fprintf(stderr, "%s: RSA, %d bits, fingerprint: %s\n", argv[0], RSA_keylen(key.rsa) * 8, print_key_fingerprint(key, KEYBLOB_RSA)); free_RSA_key(key.rsa); } else { fprintf(stderr, "Error generating/storing key\n"); } } else if (gen_key_curve) { if (argc < 1) { fprintf(stderr, "No keyfile specified\n"); exit(1); } key.ec = gen_EC_key(gen_key_curve, 0, argv[0]); if (key.key) { fprintf(stderr, "%s: ECDSA, curve %s, fingerprint: %s\n", argv[0], curve_name(get_EC_curve(key.ec)), print_key_fingerprint(key, KEYBLOB_EC)); free_EC_key(key.ec); } else { fprintf(stderr, "Error generating/storing key\n"); } } else if (del_key) { if (argc < 1) { fprintf(stderr, "No keyfile specified\n"); exit(1); } delete_container(argv[0]); } else { for (i = 0; i < argc; i++) { print_key(argv[i]); } #if defined WINDOWS && !defined OPENSSL if (argc == 0) { const char *name; while ((name = get_next_container()) != NULL) { print_key(name); } } #endif } crypto_cleanup(); return 0; } uftp-4.1.5/proxy_common.c0000644000076400007640000011273012250244021014374 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #ifdef WINDOWS #include #include #include "win_func.h" #else // if WINDOWS #include #include #include #include #include #include #endif #include "proxy.h" #include "proxy_common.h" #include "proxy_upstream.h" /** * Look for a given group in the global group list * Returns a pointer to the group in the list, or NULL if not found */ struct pr_group_list_t *find_group(uint32_t group_id, uint8_t group_inst) { int i; for (i = 0; i < MAXLIST; i++) { if ((group_list[i].group_id == group_id) && (group_list[i].group_inst == group_inst)) { return &group_list[i]; } } return NULL; } /** * Look for a given client in the group's client list * Returns the client's index in the list, or -1 if not found */ int find_client(struct pr_group_list_t *group, uint32_t id) { int i; for (i = 0; i < group->destcount; i++) { if (group->destinfo[i].id == id) { return i; } } return -1; } /** * Checks to see if the multicast address used for the given group list member * is also being used by either another member or the public address list */ int other_mcast_users(struct pr_group_list_t *group) { int i; for (i = 0; i < pub_multi_count; i++) { if (addr_equal(&group->privatemcast, &pub_multi[i])) { return 1; } } for (i = 0; i < MAXLIST; i++) { if ((&group_list[i] != group) && (group_list[i].group_id != 0) && (addr_equal(&group->privatemcast, &group_list[i].privatemcast))) { return 1; } } return 0; } /** * Clean up a group list entry. Free malloc'ed structures, drop the * multicast group (if no one else is using it) and free the slot. */ void group_cleanup(struct pr_group_list_t *group) { int i; for (i = 0; i < MAX_PEND; i++) { free(group->pending[i].naklist); } if (!addr_blank(&group->privatemcast) && (proxy_type != CLIENT_PROXY) && !other_mcast_users(group) && group->multi_join) { multicast_leave(listener, group->group_id, &group->privatemcast, m_interface, interface_count, server_fp, server_fp_count); } if (group->server_pubkey.key) { if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { free_RSA_key(group->server_pubkey.rsa); } else { free_EC_key(group->server_pubkey.ec); } if ((group->keyextype == KEYEX_ECDH_RSA) || (group->keyextype == KEYEX_ECDH_ECDSA)) { free_EC_key(group->server_dhkey.ec); free_EC_key(group->proxy_dhkey.ec); } } for (i = 0; i < group->destcount; i++) { if (group->destinfo[i].pubkey.key) { if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { free_RSA_key(group->destinfo[i].pubkey.rsa); } else { free_EC_key(group->destinfo[i].pubkey.ec); } if ((group->keyextype == KEYEX_ECDH_RSA) || (group->keyextype == KEYEX_ECDH_ECDSA)) { free_EC_key(group->destinfo[i].dhkey.ec); } } } memset(group, 0, sizeof(struct pr_group_list_t)); } /** * Initializes the uftp header of an outgoing packet */ void set_uftp_header(struct uftp_h *header, int func, struct pr_group_list_t *group) { header->version = group->version; header->func = func; header->group_id = htonl(group->group_id); header->group_inst = group->group_inst; header->src_id = uid; switch (func) { case REGISTER: case KEYINFO_ACK: case FILEINFO_ACK: case STATUS: case COMPLETE: header->seq = htons(group->send_seq_up++); break; case KEYINFO: header->seq = 0; header->src_id = group->src_id; header->grtt = quantize_grtt(group->grtt); header->gsize = quantize_gsize(group->gsize); break; } // ABORTs will set seq and src_id themselves depending on the direction } /** * Sets the timeout time for a given group list member */ void set_timeout(struct pr_group_list_t *group, int pending_reset, int rescale) { int pending, i; if (group->phase == PR_PHASE_READY) { if (!rescale) { gettimeofday(&group->start_phase_timeout_time, NULL); } group->phase_timeout_time = group->start_phase_timeout_time; add_timeval_d(&group->phase_timeout_time, 2 * group->grtt); } log5(group->group_id, 0, "set timeout: pending_reset=%d", pending_reset); for (pending = 0, i = 0; (i < MAX_PEND) && !pending; i++) { if (group->pending[i].msg != 0) { log5(group->group_id, 0, "set timeout: found pending %s", func_name(group->pending[i].msg)); pending = group->pending[i].msg; } } if (pending) { if (pending_reset) { if (!rescale) { gettimeofday(&group->start_timeout_time, NULL); } group->timeout_time = group->start_timeout_time; add_timeval_d(&group->timeout_time, 1 * group->grtt); } } else { if (!rescale) { gettimeofday(&group->start_timeout_time, NULL); } group->timeout_time = group->start_timeout_time; if (group->robust * group->grtt < 1.0) { add_timeval_d(&group->timeout_time, 1.0); } else { add_timeval_d(&group->timeout_time, group->robust * group->grtt); } } } /** * Returns the maximum number of clients that can be listed in a given message */ int max_msg_dest(struct pr_group_list_t *group, int func, int hlen) { switch (func) { case REGISTER: return (group->blocksize / sizeof(uint32_t)); case KEYINFO: return (group->blocksize / sizeof(struct destkey)); case FILEINFO_ACK: return (group->blocksize / sizeof(uint32_t)); case COMPLETE: return (group->blocksize / sizeof(uint32_t)); default: return 0; } } /** * 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: log2(group->group_id, 0, "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)); } } /** * Sends all pending aggregate message for a given group */ void send_all_pending(struct pr_group_list_t *group) { int i; for (i = 0; i < MAX_PEND; i++) { if (group->pending[i].msg != 0) { send_pending(group, i); } } } /** * Add the NAKs in the given STATUS message to the list of pending NAKs */ void add_naks_to_pending(struct pr_group_list_t *group, int pendidx, const unsigned char *message) { const unsigned char *naks; unsigned i; naks = message + sizeof(struct status_h); for (i = 0; i < group->blocksize; i++) { group->pending[pendidx].naklist[i] |= naks[i]; } } /** * 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; log3(group->group_id, 0, "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) { log3(group->group_id, 0, "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: log2(group->group_id, 0, "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]; } log3(group->group_id, 0, "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; log3(group->group_id, 0, "send time = %d.%06d", pending->tstamp.tv_sec, pending->tstamp.tv_usec); log3(group->group_id, 0, "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); log3(group->group_id, 0, "send time = %d.%06d", pending->tstamp.tv_sec, pending->tstamp.tv_usec); log3(group->group_id, 0, "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; log3(group->group_id, 0, "check_timeout: getting pending count for %s", func_name(*func)); for (total_pending = 0, i = 0; i < MAX_PEND; i++) { log3(group->group_id, 0, "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); } } } /** * Check for any client that hasn't fully registered. * If the abort parameter is set, send an ABORT to the server and client. * Returns 1 if any aren't fully registered, 0 if all are registered. */ int check_unfinished_clients(struct pr_group_list_t *group, int abort_session) { int hostidx, found; struct pr_destinfo_t *dest; if (group->keytype == KEY_NONE) { return 0; } found = 0; for (hostidx = 0; hostidx < group->destcount; hostidx++) { dest = &group->destinfo[hostidx]; if ((group->group_id != 0) && (dest->state != PR_CLIENT_READY)) { if (abort_session) { send_downstream_abort(group, dest->id, "Client not fully registered at proxy", 0); send_upstream_abort(group, dest->id, "Client not fully registered at proxy"); } found = 1; } } return found; } /** * Load a message body with the list of pending clients */ int load_pending(struct pr_group_list_t *group, int pendidx, int func, uint32_t *addrlist, int listlen) { int hostidx, cnt; struct pr_destinfo_t *dest; for (cnt = 0, hostidx = 0; (hostidx < group->destcount) && (cnt < listlen); hostidx++) { dest = &group->destinfo[hostidx]; if (dest->pending == pendidx) { addrlist[cnt++] = dest->id; dest->pending = -1; group->pending[pendidx].count--; } } if (group->pending[pendidx].count <= 0) { // Don't zero out the whole pending struct. // We need to clear the message now to set timeouts properly but // we still use the other fields just before sending the message. // The full cleanup is done in send_pending group->pending[pendidx].count = 0; group->pending[pendidx].msg = 0; } return cnt; } /** * 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 fowarding 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) { log2(group->group_id, 0, "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)) { log2(group->group_id, 0, "Signature verification failed"); free(sigcopy); return; } } if (!create_RSA_sig(group->proxy_privkey.rsa, group->hashtype, packet, meslen, sigcopy, &siglen)) { log2(group->group_id, 0, "Signature creation failed"); free(sigcopy); return; } } else { if (header->func == ENCRYPTED) { if (!verify_ECDSA_sig(key.ec, group->hashtype, packet, meslen, sigcopy, siglen)) { log2(group->group_id, 0, "Signature verification failed"); free(sigcopy); return; } } if (!create_ECDSA_sig(group->proxy_privkey.ec, group->hashtype, packet, meslen, sigcopy, &siglen)) { log2(group->group_id, 0, "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) { sockerror(group->group_id, 0, "Error forwarding message"); if ((rval = getnameinfo((struct sockaddr *)&dest, family_len(dest), destname, sizeof(destname), destport, sizeof(destport), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(group->group_id, 0, "Dest: %s:%s", destname, destport); } set_timeout(group, 0, 0); } /** * Process an HB_REQ message */ void handle_hb_request(const union sockaddr_u *src, unsigned char *packet, unsigned packetlen) { struct hb_req_h *hbreq; unsigned char *keyblob, *sig; union key_t key; unsigned char fingerprint[HMAC_LEN]; unsigned int fplen, bloblen, siglen; char destname[INET6_ADDRSTRLEN], destport[PORTNAME_LEN]; int resp, rval; hbreq = (struct hb_req_h *)(packet + sizeof(struct uftp_h)); if ((rval = getnameinfo((const struct sockaddr *)src, family_len(*src), destname, sizeof(destname), destport, sizeof(destport), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } if ((packetlen < sizeof(struct uftp_h) + (hbreq->hlen * 4)) || ((hbreq->hlen * 4) < sizeof(struct hb_req_h))) { log1(0, 0, "Rejecting HB_REQ from %s: invalid message size", destname); return; } log2(0, 0, "Received HB_REQ from %s", destname); if ((proxy_type == SERVER_PROXY) && have_down_fingerprint) { if (addr_equal(&down_addr, src)) { resp = HB_AUTH_OK; } else if (down_nonce != ntohl(hbreq->nonce)) { resp = HB_AUTH_CHALLENGE; } else { keyblob = (unsigned char *)hbreq + sizeof(struct hb_req_h); bloblen = ntohs(hbreq->bloblen); sig = keyblob + bloblen; siglen = ntohs(hbreq->siglen); // First check key fingerprint, then check signature if (keyblob[0] == KEYBLOB_RSA) { if (!import_RSA_key(&key.rsa, keyblob, bloblen)) { log2(0, 0, "Failed to import public key from HB_REQ"); resp = HB_AUTH_FAILED; goto end; } hash(HASH_SHA1, keyblob, bloblen, fingerprint, &fplen); if (memcmp(down_fingerprint, fingerprint, fplen)) { log2(0, 0, "Failed to verify HB_REQ fingerprint"); resp = HB_AUTH_FAILED; goto end; } if (!verify_RSA_sig(key.rsa, HASH_SHA1, (unsigned char *)&hbreq->nonce, sizeof(hbreq->nonce), sig, siglen)) { log2(0, 0, "Failed to verify HB_REQ signature"); resp = HB_AUTH_FAILED; goto end; } } else { if (!import_EC_key(&key.ec, keyblob, bloblen, 0)) { log2(0, 0, "Failed to import public key from HB_REQ"); resp = HB_AUTH_FAILED; goto end; } hash(HASH_SHA1, keyblob, bloblen, fingerprint, &fplen); if (memcmp(down_fingerprint, fingerprint, fplen)) { log2(0, 0, "Failed to verify HB_REQ fingerprint"); resp = HB_AUTH_FAILED; goto end; } if (!verify_ECDSA_sig(key.ec, HASH_SHA1, (unsigned char *)&hbreq->nonce, sizeof(hbreq->nonce), sig, siglen)) { log2(0, 0, "Failed to verify HB_REQ signature"); resp = HB_AUTH_FAILED; goto end; } } down_addr = *src; log2(0, 0, "Using %s:%s as downstream address:port", destname, destport); down_nonce = rand32(); resp = HB_AUTH_OK; } } else { resp = HB_AUTH_OK; } end: send_hb_response(src, resp); } /** * Process an KEY_REQ message */ void handle_key_req(const union sockaddr_u *src, const unsigned char *packet, unsigned packetlen) { const struct key_req_h *keyreq; struct timeval current_timestamp; char destname[INET6_ADDRSTRLEN]; int rval; keyreq = (const struct key_req_h *)(packet + sizeof(struct uftp_h)); if ((rval = getnameinfo((const struct sockaddr *)src, family_len(*src), destname, sizeof(destname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } if ((packetlen < sizeof(struct uftp_h) + (keyreq->hlen * 4U)) || ((keyreq->hlen * 4U) < sizeof(struct key_req_h))) { log1(0, 0, "Rejecting KEY_REQ from %s: invalid message size", destname); return; } log2(0, 0, "Received KEY_REQ from %s", destname); gettimeofday(¤t_timestamp, NULL); if (diff_sec(current_timestamp, last_key_req) > KEY_REQ_LIMIT) { send_proxy_key(); } } /** * Sends an HB_RESP in response to an HB_REQ */ void send_hb_response(const union sockaddr_u *src, int response) { unsigned char *packet; struct uftp_h *header; struct hb_resp_h *hbresp; char destname[INET6_ADDRSTRLEN], destport[PORTNAME_LEN]; int meslen, rval; packet = safe_calloc(sizeof(struct uftp_h) + sizeof(struct hb_resp_h), 1); header = (struct uftp_h *)packet; hbresp = (struct hb_resp_h *)(packet + sizeof(struct uftp_h)); header->version = UFTP_VER_NUM; header->func = HB_RESP; header->src_id = uid; hbresp->func = HB_RESP; hbresp->hlen = sizeof(struct hb_resp_h) / 4; hbresp->authenticated = response; if (response == HB_AUTH_CHALLENGE) { hbresp->nonce = htonl(down_nonce); } meslen = sizeof(struct uftp_h) + sizeof(struct hb_resp_h); if (nb_sendto(listener, packet, meslen, 0, (const struct sockaddr *)src, family_len(*src)) == SOCKET_ERROR) { sockerror(0, 0, "Error sending HB_RESP"); } else { if ((rval = getnameinfo((const struct sockaddr *)src, family_len(*src), destname, sizeof(destname), destport, sizeof(destport), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Sent HB_RESP to %s:%s", destname, destport); } free(packet); } /** * Sends a PROXY_KEY message to the first listed public multicast address. */ void send_proxy_key() { unsigned char *packet, *keyblob, *dhblob, *sig; struct uftp_h *header; struct proxy_key_h *proxykey; uint32_t nonce; unsigned int meslen, siglen; uint16_t bloblen, dhlen; char pubname[INET6_ADDRSTRLEN]; int rval; packet = safe_calloc(sizeof(struct uftp_h) + sizeof(struct hb_req_h) + (PUBKEY_LEN * 3) , 1); header = (struct uftp_h *)packet; proxykey = (struct proxy_key_h *)(packet + sizeof(struct uftp_h)); keyblob = (unsigned char *)proxykey + sizeof(struct proxy_key_h); header->version = UFTP_VER_NUM; header->func = PROXY_KEY; header->src_id = uid; proxykey->func = PROXY_KEY; nonce = htonl(rand32()); proxykey->nonce = nonce; if (privkey_type[0] == KEYBLOB_RSA) { if (!export_RSA_key(privkey[0].rsa, keyblob, &bloblen)) { log2(0, 0, "Error exporting public key"); free(packet); return; } } else { if (!export_EC_key(privkey[0].ec, keyblob, &bloblen)) { log2(0, 0, "Error exporting public key"); free(packet); return; } } dhblob = keyblob + bloblen; if (dhkey.key) { if (!export_EC_key(dhkey.ec, dhblob, &dhlen)) { log2(0, 0, "Error exporting public key"); free(packet); return; } } else { dhlen = 0; } sig = dhblob + dhlen; if (privkey_type[0] == KEYBLOB_RSA) { if (!create_RSA_sig(privkey[0].rsa, HASH_SHA1, (unsigned char *)&nonce, sizeof(nonce), sig, &siglen)) { log2(0, 0, "Error signing nonce"); free(packet); return; } } else { if (!create_ECDSA_sig(privkey[0].ec, HASH_SHA1, (unsigned char *)&nonce, sizeof(nonce), sig, &siglen)) { log2(0, 0, "Error signing nonce"); free(packet); return; } } proxykey->bloblen = htons(bloblen); proxykey->dhlen = htons(dhlen); proxykey->siglen = htons(siglen); proxykey->hlen = (sizeof(struct proxy_key_h) + bloblen + dhlen + siglen)/4; meslen = sizeof(struct uftp_h) + (proxykey->hlen * 4); if (nb_sendto(listener, packet, meslen, 0, (struct sockaddr *)&pub_multi[0], family_len(pub_multi[0])) == SOCKET_ERROR) { sockerror(0, 0, "Error sending PROXY_KEY"); } else { if ((rval = getnameinfo((struct sockaddr *)&pub_multi[0], family_len(pub_multi[0]), pubname, sizeof(pubname), NULL, 0, NI_NUMERICHOST)) != 0) { log1(0, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(0, 0, "Sent PROXY_KEY to %s", pubname); } free(packet); } /** * Sends an ABORT message upstream to a server */ void send_upstream_abort(struct pr_group_list_t *group, uint32_t addr, const char *message) { unsigned char *buf; struct uftp_h *header; struct abort_h *abort_hdr; int payloadlen; 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); header->seq = group->send_seq_up++; header->src_id = uid; abort_hdr->func = ABORT; abort_hdr->hlen = sizeof(struct abort_h) / 4; abort_hdr->flags = 0; abort_hdr->host = addr; strncpy(abort_hdr->message, message, sizeof(abort_hdr->message) - 1); payloadlen = sizeof(struct uftp_h) + sizeof(struct abort_h); // Proxies should never need to send an encrypted ABORT if (nb_sendto(listener, buf, payloadlen, 0, (struct sockaddr *)&group->up_addr, family_len(group->up_addr)) == SOCKET_ERROR) { sockerror(group->group_id, 0, "Error sending ABORT"); } if (addr == 0) { group_cleanup(group); } free(buf); } /** * Sends an ABORT message downstream to clients */ void send_downstream_abort(struct pr_group_list_t *group, uint32_t dest_id, const char *message, int current) { unsigned char *buf; struct uftp_h *header; struct abort_h *abort_hdr; int payloadlen; 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); header->seq = group->send_seq_down++; header->src_id = uid; abort_hdr->func = ABORT; abort_hdr->hlen = sizeof(struct abort_h) / 4; if ((dest_id == 0) && current) { abort_hdr->flags |= FLAG_CURRENT_FILE; } abort_hdr->host = dest_id; strncpy(abort_hdr->message, message, sizeof(abort_hdr->message) - 1); payloadlen = sizeof(struct uftp_h) + sizeof(struct abort_h); // Proxies should never need to send an encrypted ABORT if (nb_sendto(listener, buf, payloadlen, 0, (struct sockaddr *)&group->privatemcast, family_len(group->privatemcast)) == SOCKET_ERROR) { sockerror(group->group_id, 0, "Error sending ABORT"); } free(buf); } /** * 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))) { log2(group->group_id,0, "Rejecting ABORT from %s: invalid message size", upstream ? "server" : "client"); } if (upstream) { if ((abort_hdr->host == 0) || abort_hdr->host == uid ) { log2(group->group_id, 0, "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) { log2(group->group_id, 0, "Transfer aborted by %s: %s", group->destinfo[hostidx].name, abort_hdr->message); } else { log2(group->group_id, 0, "Transfer aborted by %08X: %s", ntohl(src_id), abort_hdr->message); } send_upstream_abort(group, src_id, abort_hdr->message); } } /** * Verifies a server's or client's public key fingerprint * Returns 1 on success, 0 on failure */ int verify_fingerprint(const struct fp_list_t *fplist, int listlen, const unsigned char *keyblob, uint16_t bloblen, struct pr_group_list_t *group, uint32_t id) { unsigned char fingerprint[HMAC_LEN]; unsigned int fplen; int found, keyidx; if (listlen == 0) { return 1; } for (keyidx = 0, found = 0; (keyidx < listlen) && !found; keyidx++) { if (fplist[keyidx].uid == id) { keyidx--; found = 1; } } if (!found) { return 0; } if (!fplist[keyidx].has_fingerprint) { return 1; } hash(HASH_SHA1, keyblob, bloblen, fingerprint, &fplen); if (memcmp(fplist[keyidx].fingerprint, fingerprint, fplen)) { return 0; } else { return 1; } } /** * Returns the verify_data string used in certain messages. This value * is then run through the PRF with the result going into the message. */ uint8_t *build_verify_data(struct pr_group_list_t *group, int hostidx, int *verifylen, int full) { uint8_t *verifydata, *keyblob; uint32_t group_id; struct pr_destinfo_t *dest; union key_t key; int iplen; uint16_t bloblen; iplen = (group->privatemcast.ss.ss_family == AF_INET6) ? sizeof(struct in6_addr) : sizeof(struct in_addr); if (hostidx != -1) { dest = &group->destinfo[hostidx]; } *verifylen = 0; if (!full) { verifydata = safe_calloc(sizeof(group->group_id) + iplen + sizeof(group->rand1) + sizeof(group->rand2) + sizeof(group->premaster), 1); } else { verifydata = safe_calloc(sizeof(group->group_id) + iplen + sizeof(group->rand1) + sizeof(group->rand2) + sizeof(group->premaster) + PUBKEY_LEN + sizeof(group->groupmaster), 1); } group_id = htonl(group->group_id); memcpy(verifydata, &group_id, sizeof(group_id)); *verifylen += sizeof(group_id); if (group->privatemcast.ss.ss_family == AF_INET6) { memcpy(verifydata + *verifylen, &group->privatemcast.sin6.sin6_addr.s6_addr, iplen); } else { memcpy(verifydata + *verifylen, &group->privatemcast.sin.sin_addr.s_addr, iplen); } *verifylen += iplen; memcpy(verifydata + *verifylen, group->rand1, sizeof(group->rand1)); *verifylen += sizeof(group->rand1); if (hostidx == -1) { memcpy(verifydata + *verifylen, group->rand2, sizeof(group->rand2)); *verifylen += sizeof(group->rand2); memcpy(verifydata + *verifylen, group->premaster, group->premaster_len); *verifylen += group->premaster_len; } else { memcpy(verifydata + *verifylen, dest->rand2, sizeof(dest->rand2)); *verifylen += sizeof(dest->rand2); memcpy(verifydata + *verifylen, dest->premaster, dest->premaster_len); *verifylen += dest->premaster_len; } if (full) { if (group->client_auth) { if (hostidx == -1) { key = group->proxy_privkey; } else { key = dest->pubkey; } keyblob = verifydata + *verifylen; if ((group->keyextype == KEYEX_RSA) || (group->keyextype == KEYEX_ECDH_RSA)) { if (!export_RSA_key(key.rsa, keyblob, &bloblen)) { free(verifydata); return NULL; } } else { if (!export_EC_key(key.ec, keyblob, &bloblen)) { free(verifydata); return NULL; } } *verifylen += bloblen; } memcpy(verifydata + *verifylen, group->groupmaster, sizeof(group->groupmaster)); *verifylen += sizeof(group->groupmaster); } return verifydata; } uftp-4.1.5/proxy_loop.h0000644000076400007640000000264612250244021014066 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_LOOP_H #define _PROXY_LOOP_H void mainloop(void); #endif // _PROXY_LOOP_H uftp-4.1.5/client_announce.h0000644000076400007640000000357112250244021015016 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _CLIENT_ANNOUNCE_H #define _CLIENT_ANNOUNCE_H void handle_announce(union sockaddr_u *src, unsigned char *packet, unsigned packetlen, struct timeval rxtime); void handle_keyinfo(struct group_list_t *group, unsigned char *message, unsigned meslen, uint32_t src_id); void handle_regconf(struct group_list_t *group, const unsigned char *message, unsigned meslen); void send_register(struct group_list_t *group); void send_keyinfo_ack(struct group_list_t *group); #endif // _CLIENT_ANNOUNCE_H uftp-4.1.5/testclient_multi.c0000644000076400007640000007117612137013343015250 0ustar dbushdbush#include #include #include #include #ifdef WINDOWS #include #include #include "win_func.h" #else #include #include #include #include #include #include #endif #include "uftp.h" #include "uftp_common.h" #include "encryption.h" #define BLOCKSIZE 1500 SOCKET sock; uint8_t groupmaster[MASTER_LEN]; uint8_t groupkey[MAXKEY], groupiv[MAXIV], hmackey[HMAC_LEN]; uint32_t myaddr; int groupid, fileid, fsize, block_count, section_count; int blocksize, payloadsize, encpayloadsize; int hashtype, keytype, keylen, ivlen, hashlen; RSA_key_t rsakey; int phase, foundgroup; int destcount; uint8_t rand1[RAND_LEN]; struct destinfo_t { struct in_addr addr; uint8_t rand2[RAND_LEN]; uint8_t premaster[MASTER_LEN]; uint8_t premaster_enc[300]; int premaster_enc_len; uint8_t master[MASTER_LEN]; uint8_t key[MAXKEY]; uint8_t iv[MAXIV]; uint8_t hmac[HMAC_LEN]; uint8_t verify[1000]; int verifylen; int gotkeyinfo; int gotfileinfo; } *destlist; void send_register(struct sockaddr_in sin) { unsigned char buf[BLOCKSIZE], seed[64], prf_buf[200]; struct uftp_h *header; struct register_h *reg; unsigned char *reg_premaster; unsigned int explen; int prf_len, i; header = (struct uftp_h *)buf; reg = (struct register_h *)(buf + sizeof(struct uftp_h)); reg_premaster = buf + sizeof(struct uftp_h) + sizeof(struct register_h); for (i=0;iuftp_id = UFTP_VER_NUM; header->func = REGISTER; header->blsize = htons(sizeof(struct register_h) + destlist[i].premaster_enc_len); header->group_id = htonl(groupid); header->srcaddr = destlist[i].addr.s_addr; header->destaddr = sin.sin_addr.s_addr; reg->func = REGISTER; reg->destcount = 0; memcpy(reg->rand2,destlist[i].rand2,sizeof(destlist[i].rand2)); memcpy(reg_premaster, destlist[i].premaster_enc,sizeof(destlist[i].premaster_enc)); reg->premaster_len = htons(destlist[i].premaster_enc_len); if (sendto(sock, buf, sizeof(struct uftp_h) + ntohs(header->blsize), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1) { sockerror(0,0,"sendto failed for REGISTER"); exit(1); } // calculate session keys memcpy(seed,rand1,sizeof(rand1)); memcpy(seed+sizeof(rand1), destlist[i].rand2,sizeof(destlist[i].rand2)); PRF(hashtype, MASTER_LEN,destlist[i].premaster, sizeof(destlist[i].premaster), "master secret", seed, sizeof(seed), prf_buf,&prf_len); memcpy(destlist[i].master,prf_buf, sizeof(destlist[i].master)); //printhex("rand1",destlist[i].rand1,sizeof(destlist[i].rand1)); //printhex("rand2",destlist[i].rand2,sizeof(destlist[i].rand2)); //printhex("premaster",destlist[i].premaster,sizeof(destlist[i].premaster)); //printhex("master",destlist[i].master,sizeof(destlist[i].master)); explen = hashlen + keylen + ivlen; PRF(hashtype, explen, destlist[i].master, sizeof(destlist[i].master), "key expansion", seed, sizeof(seed), prf_buf,&prf_len); memcpy(destlist[i].hmac, prf_buf, hashlen); memcpy(destlist[i].key, prf_buf + hashlen, keylen); memcpy(destlist[i].iv, prf_buf + hashlen + keylen, ivlen); //printhex("local hmac", destlist[i].hmac, hashlen); //printhex("local key", destlist[i].key, keylen); //printhex("local iv", destlist[i].iv, ivlen); } } void handle_announce(unsigned char *buf, struct sockaddr_in sin) { struct uftp_h *header; struct announce_h *announce; unsigned char *keymod; uint32_t keyexp, n_groupid; uint16_t modlen; struct ip_mreq multi; int i; header = (struct uftp_h *)buf; announce = (struct announce_h *)(buf + sizeof(struct uftp_h)); keymod = buf + sizeof(struct uftp_h) + sizeof(struct announce_h); fprintf(stderr,"Received ANNOUNCE from %s\n", inet_ntoa(sin.sin_addr)); memcpy(rand1,announce->rand1,sizeof(announce->rand1)); keyexp = ntohl(announce->keyexp); modlen = ntohs(announce->keylen); if (!import_RSA_key(&rsakey, keyexp, keymod, modlen)) { fprintf(stderr, "Failed to import public key"); exit(0); } groupid = ntohl(header->group_id); keytype = announce->keytype; hashtype = announce->hashtype; get_key_info(keytype, &keylen, &ivlen); hashlen = get_hash_len(hashtype); payloadsize = ntohs(announce->mtu) - 28 - sizeof(struct uftp_h); encpayloadsize = payloadsize - sizeof(struct encrypted_h) - 16 - hashlen; blocksize = encpayloadsize - sizeof(struct fileseg_h); fprintf(stderr,"bsize=%d, epsize=%d, psize=%d\n", blocksize, encpayloadsize, payloadsize); multi.imr_multiaddr.s_addr=announce->privatemcast; multi.imr_interface.s_addr=htonl(INADDR_ANY); if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multi,sizeof(multi))==-1) { perror("Error joining multicast group"); closesocket(sock); exit(1); } // Assume open group membership, and respond for all // Assume encryption enabled with HMAC signatures for (i=0;iprivatemcast, sizeof(announce->privatemcast)); destlist[i].verifylen += sizeof(announce->privatemcast); memcpy(destlist[i].verify + destlist[i].verifylen,rand1,sizeof(rand1)); destlist[i].verifylen += sizeof(rand1); memcpy(destlist[i].verify + destlist[i].verifylen, destlist[i].rand2, sizeof(destlist[i].rand2)); destlist[i].verifylen += sizeof(destlist[i].rand2); memcpy(destlist[i].verify + destlist[i].verifylen,destlist[i].premaster, sizeof(destlist[i].premaster)); destlist[i].verifylen += sizeof(destlist[i].premaster); if (!RSA_encrypt(rsakey, destlist[i].premaster, sizeof(destlist[i].premaster), destlist[i].premaster_enc, &destlist[i].premaster_enc_len)) { fprintf(stderr, "couldn't encrypt"); exit(1); } } foundgroup = 0; phase = KEYINFO; send_register(sin); } void send_infoack(struct sockaddr_in sin, int dest, int request) { unsigned char buf[BLOCKSIZE], data[BLOCKSIZE]; unsigned char iv[MAXIV], prf_buf[200], hmac[HMAC_LEN]; struct uftp_h *header; struct encrypted_h *encrypted; struct infoack_h *infoack; unsigned char *enc_sig, *enc_payload; int prf_len; unsigned int len; struct timeval tv; header = (struct uftp_h *)buf; encrypted=(struct encrypted_h *)(buf + sizeof(struct uftp_h)); enc_sig=(unsigned char *)encrypted + sizeof(struct encrypted_h); enc_payload=enc_sig + hashlen; infoack = (struct infoack_h *)data; memset(buf, 0, sizeof(buf)); memset(data, 0, sizeof(data)); gettimeofday(&tv, NULL); infoack->func = INFO_ACK; infoack->destcount = 0; if (request == KEYINFO) { infoack->file_id = 0; hash(hashtype, destlist[dest].verify, destlist[dest].verifylen, hmac, &len); PRF(hashtype, VERIFY_LEN,groupmaster, sizeof(groupmaster), "client finished", hmac, len, prf_buf, &prf_len); memcpy(infoack->verify_data,prf_buf,sizeof(infoack->verify_data)); } else if (request == FILEINFO) { infoack->file_id = ntohs(fileid); } else { fprintf(stderr, "wrong request for INFO_ACK: %s\n", func_name(request)); exit(1); } //printhex("encrypted INFO_ACK", infoack, sizeof(struct infoack_h)); build_iv(iv, groupiv, ivlen, htonl(groupid), destlist[dest].addr.s_addr, htonl(tv.tv_sec), htonl(tv.tv_usec)); if (!encrypt_block(keytype, iv, groupkey, data, sizeof(struct infoack_h), enc_payload, &len)) { fprintf(stderr, "encrypt failed for INFO_ACK"); exit(1); } header->uftp_id = UFTP_VER_NUM; header->func = ENCRYPTED; header->blsize = htons(sizeof(struct encrypted_h) + hashlen + len); header->group_id = htonl(groupid); header->srcaddr = destlist[dest].addr.s_addr; header->destaddr = sin.sin_addr.s_addr; encrypted->tstamp_sec = htonl(tv.tv_sec); encrypted->tstamp_usec = htonl(tv.tv_usec); encrypted->sig_len = htons(hashlen); encrypted->payload_len = htons(len); create_hmac(hashtype, hmackey, hashlen, buf, sizeof(struct uftp_h) + ntohs(header->blsize), hmac, &len); if (len!=hashlen) { fprintf(stderr,"invalid hmac len: %d\n",len); exit(1); } memcpy(enc_sig, hmac, len); if (sendto(sock, buf, sizeof(struct uftp_h) + ntohs(header->blsize), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1) { sockerror(0,0,"sendto failed for INFO_ACK"); exit(1); } } void handle_keyinfo(unsigned char *buf, struct sockaddr_in sin) { unsigned char dec_groupmaster[MASTER_LEN], prf_buf[200], iv[MAXIV]; struct uftp_h *header; struct keyinfo_h *keyinfo; struct destkey *dkey; int i, j, found; unsigned int len, explen; int prf_len, gotall; header = (struct uftp_h *)buf; keyinfo = (struct keyinfo_h *)(buf + sizeof(struct uftp_h)); dkey = (struct destkey *)((char *)keyinfo + sizeof(struct keyinfo_h)); fprintf(stderr,"Received KEYINFO from %s\n", inet_ntoa(sin.sin_addr)); for (j=0;jdestcount;j++) { for (i=0,found=0;(igroupmaster_len)); /* decrypt group key */ build_iv(iv, destlist[i].iv, ivlen, htonl(groupid), header->srcaddr, keyinfo->tstamp_sec, keyinfo->tstamp_usec); if (!decrypt_block(keytype, iv, destlist[i].key, dkey[j].groupmaster, keyinfo->groupmaster_len, dec_groupmaster, &len)) { fprintf(stderr, "decrypt failed for group master"); exit(1); } if (!foundgroup) { groupmaster[0]=header->uftp_id; memcpy(groupmaster+1,dec_groupmaster,len); printhex("group master",groupmaster, sizeof(groupmaster)); foundgroup=1; explen = hashlen + keylen + ivlen; PRF(hashtype, explen, groupmaster, sizeof(groupmaster), "key expansion", rand1, sizeof(rand1), prf_buf, &prf_len); memcpy(hmackey, prf_buf, hashlen); memcpy(groupkey, prf_buf + hashlen, keylen); memcpy(groupiv, prf_buf + hashlen + keylen, ivlen); printhex("hmackey",hmackey,hashlen); printhex("groupkey",groupkey,keylen); printhex("groupiv",groupiv,ivlen); } else { if (memcmp(groupmaster+1, dec_groupmaster, len)) { fprintf(stderr, "decrypted group master " "doesn't match prior value"); exit(1); } } if (!destlist[i].gotkeyinfo) { memcpy(destlist[i].verify+destlist[i].verifylen, groupmaster, sizeof(groupmaster)); destlist[i].verifylen+=sizeof(groupmaster); destlist[i].gotkeyinfo=1; } send_infoack(sin, i, KEYINFO); } for (i=0,gotall=1;(ifile_id); // Assuming file is < 4GB fsize = ntohl(fileinfo->lofsize); block_count = ntohl(fileinfo->block_total); section_count = ntohs(fileinfo->section_total); for (j=0;jdestcount);j++) { for (i=0,found=0;(iuftp_id!=UFTP_VER_NUM) { fprintf(stderr,"Invalid version number\n"); exit(1); } if (recv_lenblsize)) { fprintf(stderr,"Invalid packet size: %d\n", recv_len); exit(1); } if (header->func==ENCRYPTED) { encrypted=(struct encrypted_h *)(buf + sizeof(struct uftp_h)); enc_sig=(unsigned char *)encrypted + sizeof(struct encrypted_h); enc_payload=enc_sig + hashlen; if (phase != FILEINFO) { fprintf(stderr, "not expecting encrypted message\n"); exit(1); } // verify HMAC (assume sig is not RSA for now) memcpy(hmacsav,enc_sig,ntohs(encrypted->sig_len)); memset(enc_sig, 0, ntohs(encrypted->sig_len)); create_hmac(hashtype, hmackey, hashlen, buf, recv_len, hmac, &len); if (len!=hashlen) { fprintf(stderr,"invalid hmac len: %d\n",len); exit(1); } if (!memcmp(hmacsav,hmac,hashlen)) { fprintf(stderr,"hmac matches!\n"); } else { fprintf(stderr,"hmac mismatch\n"); printhex("calculated value",hmac,len); printhex("expected value",hmacsav,sizeof(hmacsav)); exit(1); } // decrypt message build_iv(iv, groupiv, ivlen, htonl(groupid), header->srcaddr, encrypted->tstamp_sec, encrypted->tstamp_usec); if (!decrypt_block(keytype, iv, groupkey, enc_payload, ntohs(encrypted->payload_len), data, &len)) { fprintf(stderr, "decrypt failed\n"); exit(1); } func = (char *)data; if (*func != FILEINFO) { fprintf(stderr, "Decrypted message not FILEINFO\n"); exit(1); } } else { func = (char *)buf + sizeof(struct uftp_h); } switch (*func) { case ANNOUNCE: if (phase == ANNOUNCE) { handle_announce(buf, sin); } break; case KEYINFO: if (phase == KEYINFO || phase == FILEINFO) { handle_keyinfo(buf, sin); } break; case FILEINFO: if (phase == FILEINFO) { handle_fileinfo(data, sin); } break; default: fprintf(stderr,"Invalid function: %d\n",header->func); exit(1); } } while (phase != FILESEG); } void send_status(struct sockaddr_in sin, int dest, int last_file_id, int pass, int section, unsigned char *naks, int nak_count) { unsigned char buf[BLOCKSIZE], data[BLOCKSIZE]; unsigned char iv[MAXIV], hmac[HMAC_LEN]; struct uftp_h *header; struct encrypted_h *encrypted; struct status_h *status; unsigned char *enc_sig, *enc_payload, *naklist; unsigned int len, dlen; struct timeval tv; header = (struct uftp_h *)buf; encrypted=(struct encrypted_h *)(buf + sizeof(struct uftp_h)); enc_sig=(unsigned char *)encrypted + sizeof(struct encrypted_h); enc_payload=enc_sig + hashlen; status = (struct status_h *)data; naklist = data + sizeof(struct status_h); memset(buf, 0, sizeof(buf)); memset(data, 0, sizeof(data)); gettimeofday(&tv, NULL); status->func = STATUS; status->file_id = ntohs(last_file_id); status->pass = pass; status->section = ntohs(section); status->nak_count = ntohl(nak_count); if (nak_count) { memcpy(naklist, naks, blocksize); dlen = sizeof(struct status_h) + blocksize; } else { dlen = sizeof(struct status_h); } build_iv(iv, groupiv, ivlen, htonl(groupid), destlist[dest].addr.s_addr, htonl(tv.tv_sec), htonl(tv.tv_usec)); if (!encrypt_block(keytype, iv, groupkey, data, dlen, enc_payload, &len)) { fprintf(stderr, "encrypt failed for STATUS"); exit(1); } header->uftp_id = UFTP_VER_NUM; header->func = ENCRYPTED; header->blsize = htons(sizeof(struct encrypted_h) + hashlen + len); header->group_id = htonl(groupid); header->srcaddr = destlist[dest].addr.s_addr; header->destaddr = sin.sin_addr.s_addr; encrypted->tstamp_sec = htonl(tv.tv_sec); encrypted->tstamp_usec = htonl(tv.tv_usec); encrypted->sig_len = htons(hashlen); encrypted->payload_len = htons(len); create_hmac(hashtype, hmackey, hashlen, buf, sizeof(struct uftp_h) + ntohs(header->blsize), hmac, &len); if (len!=hashlen) { fprintf(stderr,"invalid hmac len: %d\n",len); exit(1); } memcpy(enc_sig, hmac, len); if (sendto(sock, buf, sizeof(struct uftp_h) + ntohs(header->blsize), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1) { sockerror(0,0,"sendto failed for STATUS"); exit(1); } } void send_complete(struct sockaddr_in sin, int dest, int last_file_id) { unsigned char buf[BLOCKSIZE], data[BLOCKSIZE]; unsigned char iv[MAXIV], hmac[HMAC_LEN]; struct uftp_h *header; struct encrypted_h *encrypted; struct complete_h *complete; unsigned char *enc_sig, *enc_payload; unsigned int len; struct timeval tv; header = (struct uftp_h *)buf; encrypted=(struct encrypted_h *)(buf + sizeof(struct uftp_h)); enc_sig=(unsigned char *)encrypted + sizeof(struct encrypted_h); enc_payload=enc_sig + hashlen; complete = (struct complete_h *)data; memset(buf, 0, sizeof(buf)); memset(data, 0, sizeof(data)); gettimeofday(&tv, NULL); complete->func = COMPLETE; complete->file_id = ntohs(last_file_id); build_iv(iv, groupiv, ivlen, htonl(groupid), destlist[dest].addr.s_addr, htonl(tv.tv_sec), htonl(tv.tv_usec)); if (!encrypt_block(keytype, iv, groupkey, data, sizeof(struct complete_h), enc_payload, &len)) { fprintf(stderr, "encrypt failed for COMPLETE"); exit(1); } header->uftp_id = UFTP_VER_NUM; header->func = ENCRYPTED; header->blsize = htons(sizeof(struct encrypted_h) + hashlen + len); header->group_id = htonl(groupid); header->srcaddr = destlist[dest].addr.s_addr; header->destaddr = sin.sin_addr.s_addr; encrypted->tstamp_sec = htonl(tv.tv_sec); encrypted->tstamp_usec = htonl(tv.tv_usec); encrypted->sig_len = htons(hashlen); encrypted->payload_len = htons(len); create_hmac(hashtype, hmackey, hashlen, buf, sizeof(struct uftp_h) + ntohs(header->blsize), hmac, &len); if (len!=hashlen) { fprintf(stderr,"invalid hmac len: %d\n",len); exit(1); } memcpy(enc_sig, hmac, len); if (sendto(sock, buf, sizeof(struct uftp_h) + ntohs(header->blsize), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1) { sockerror(0,0,"sendto failed for COMPLETE"); exit(1); } } void recv_data() { uint8_t buf[BLOCKSIZE], hmac[HMAC_LEN], hmacsav[HMAC_LEN], data[BLOCKSIZE]; struct uftp_h *header; struct encrypted_h *encrypted; struct fileseg_h *fileseg; struct done_h *done; struct doneconf_h *doneconf; unsigned char *enc_sig, *enc_payload; unsigned char *decdata, iv[MAXIV], naklist[BLOCKSIZE]; int i, j, found, cnt, addr_len, recv_len; unsigned int hlen, decodelen; struct sockaddr_in sin; struct timeval t_start, t_end; double t_diff; char *naks, *func; uint32_t *addrlist; int section_offset, blocks_this_sec, nakidx, naklistidx; header = (struct uftp_h *)buf; encrypted = (struct encrypted_h *)(buf + sizeof(struct uftp_h)); enc_sig = (unsigned char *)encrypted + sizeof(struct encrypted_h); enc_payload = enc_sig + hashlen; func = (char *)data; fileseg = (struct fileseg_h *)data; done = (struct done_h *)data; doneconf = (struct doneconf_h *)data; decdata = data + sizeof (struct fileseg_h); naks = malloc(block_count); for (i=0;iuftp_id!=UFTP_VER_NUM) { fprintf(stderr,"Invalid version number\n"); exit(1); } if (recv_len!=sizeof(struct uftp_h)+htons(header->blsize)) { fprintf(stderr,"Invalid packet size: %d\n", recv_len); exit(1); } if (header->func!=ENCRYPTED) { fprintf(stderr,"Invalid function: %d\n",header->func); exit(1); } // verify HMAC (assume sig is not RSA for now) memcpy(hmacsav,enc_sig,ntohs(encrypted->sig_len)); memset(enc_sig, 0, ntohs(encrypted->sig_len)); create_hmac(hashtype, hmackey, hashlen, buf, recv_len, hmac, &hlen); if (hlen!=hashlen) { fprintf(stderr,"invalid hmac len: %d\n",hlen); exit(1); } if (memcmp(hmacsav,hmac,hashlen)) { fprintf(stderr,"hmac mismatch\n"); printhex("calculated value",hmac,hlen); printhex("expected value",hmacsav,sizeof(hmacsav)); exit(1); } /* decrypt pakcet */ build_iv(iv, groupiv, ivlen, htonl(groupid), header->srcaddr, encrypted->tstamp_sec, encrypted->tstamp_usec); if (!decrypt_block(keytype, iv, groupkey, enc_payload, ntohs(encrypted->payload_len), data, &decodelen)) { fprintf(stderr, "decrypt failed\n"); exit(1); } if (*func == FILESEG) { int seq = ntohl(fileseg->seq_num); if (seq > block_count) { fprintf(stderr,"invalid seq_num: %d\n", seq); } else { naks[seq] = 0; } } else if (*func == DONE) { int pass, section, last_file_id; pass = done->pass; section = ntohs(done->section); last_file_id = ntohs(done->file_id); addrlist = (uint32_t *)((char *)done + sizeof(struct done_h)); ////// section_offset = (blocksize * 8) * (section - 1); blocks_this_sec = ((section < section_count) ? (blocksize * 8) : (block_count % (blocksize * 8))); if (section_count && !blocks_this_sec) { blocks_this_sec = blocksize * 8; } memset(naklist, 0, sizeof(naklist)); for (i = 0; i < blocks_this_sec; i++) { nakidx = i + section_offset; naklistidx = i; if (naks[nakidx]) { log(0, 0, "NAK for %d", nakidx); naklist[naklistidx >> 3] |= (1 << (naklistidx & 7)); } } ////// for (j=0;jdestcount);j++) { for (i=0,found=0;(i10000) { fprintf(stderr,"invalid destcount\n"); exit(1); } destlist = calloc(destcount, sizeof(struct destinfo_t)); crypto_init(); announce_phase(); recv_data(); crypto_cleanup(); free(destlist); #ifdef WINDOWS WSACleanup(); #endif return 0; } uftp-4.1.5/proxy_config.h0000644000076400007640000000475512176066071014403 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_CONFIG_H #define _PROXY_CONFIG_H #define DEF_HB_INT 20 #define DEF_PORT "1044" #define DEF_PUB_MULTI "230.4.4.1" #define DEF_RCVBUF 262144 #define DEF_BSD_RCVBUF 233016 #define DEF_TTL 1 #define DEF_DSCP 0 #ifdef WINDOWS #define DEF_LOGFILE "C:\\uftpproxyd_log.txt" #elif defined VMS #define DEF_LOGFILE "SYS$SCRATCH:uftpproxyd_log.txt" #else #define DEF_LOGFILE "/tmp/uftpproxyd.log" #endif #define USAGE \ "uftpproxyd { -s { dest | fp=fingerprint } | -c | -r } [ -d ] [ -p port ]\n\ [ -t ttl ] [ -Q dscp ] [ -N priority ] [ -O out_multi_interface ]\n\ [ -U UID ] [ -q dest_port ] [ -m ] [ -x log_level ]\n\ [ -H hb_server[:port][,hb_server[:port]...] ] [ -h hb_interval ]\n\ [ -g max_log_size ] [ -n max_log_count ]\n\ [ -B udp_buf_size ] [ -L logfile ] [ -P pidfile ] [ -C clientlist_file ]\n\ [ -S serverlist_file ] [ -e ecdh_curve ] [ -k keyfile[,keyfile...] ]\n\ [ -K rsa:key_length | ec:curve[,rsa:key_length | ec:curve...]]\n\ [ -I interface[,interface...] ] [ -M pub_mcast_addr[,pub_mcast_addr...] ]\n" void process_args(int argc, char *argv[]); #endif // _PROXY_CONFIG_H uftp-4.1.5/proxy_downstream.h0000644000076400007640000000460512250244021015275 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_DOWNSTREAM_H #define _PROXY_DOWNSTREAM_H void handle_register(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen, uint32_t src); void handle_clientkey(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen, uint32_t src); void handle_keyinfo_ack(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen); void handle_fileinfo_ack(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen); void send_keyinfo(struct pr_group_list_t *group, const uint32_t *addrlist, int addrlen); void handle_status(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen); void handle_complete(struct pr_group_list_t *group, int hostidx, const unsigned char *message, unsigned meslen); #endif // _PROXY_DOWNSTREAM_H uftp-4.1.5/server_main.c0000644000076400007640000000310412137013343014154 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2010 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include "server_config.h" #include "server_init.h" #include "server_send.h" int main(int argc, char *argv[]) { int rval; pre_initialize(); process_args(argc, argv); initialize(); rval = send_files(); return rval ? 0 : 1; } uftp-4.1.5/proxy_init.c0000644000076400007640000004435612250244021014057 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include #include "win_func.h" #else // WINDOWS #include #include #include #include #include #endif #include "proxy.h" #include "proxy_init.h" #include "proxy_config.h" #include "proxy_common.h" static int parent; // is this the parent process that exits after a fork? /** * Cleanup routine set up by atexit */ void cleanup(void) { int i; for (i = 0; i < MAXLIST; i++) { if (group_list[i].group_id != 0) { group_cleanup(&group_list[i]); } } if (!parent) { for (i = 0; i < pub_multi_count; i++) { multicast_leave(listener, 0, &pub_multi[i], m_interface, interface_count, server_fp, server_fp_count); } } closesocket(listener); for (i = 0; i < key_count; i++) { if (privkey_type[i] == KEYBLOB_RSA) { free_RSA_key(privkey[i].rsa); } else { free_EC_key(privkey[i].ec); } } crypto_cleanup(); #ifdef WINDOWS WSACleanup(); #endif fclose(stderr); } /** * Generic signal handler, exits on signal */ void gotsig(int sig) { log0(0, 0, "Exiting on signal %d", sig); exit(0); } /** * Signal handler for SIGPIPE */ void gotpipe(int sig) { log2(0, 0, "Got SIGPIPE"); } #ifdef WINDOWS /** * Windows event handler, exits */ BOOL WINAPI winsig(DWORD event) { switch (event) { case CTRL_C_EVENT: log0(0, 0, "Got CTRL_C_EVENT"); break; case CTRL_BREAK_EVENT: log0(0, 0, "Got CTRL_BREAK_EVENT"); break; case CTRL_CLOSE_EVENT: log0(0, 0, "Got CTRL_CLOSE_EVENT"); break; case CTRL_LOGOFF_EVENT: log0(0, 0, "Got CTRL_LOGOFF_EVENT"); break; case CTRL_SHUTDOWN_EVENT: log0(0, 0, "Got CTRL_SHUTDOWN_EVENT"); break; default: log0(0, 0, "GOT unknown event", event); break; } exit(0); } #endif /** * Do initial setup before parsing arguments, including getting interface list */ void pre_initialize(void) { #ifdef WINDOWS struct WSAData data; if (WSAStartup(2, &data)) { fprintf(stderr, "Error in WSAStartup: %d\n", WSAGetLastError()); exit(1); } #endif applog = stderr; ifl_len = sizeof(ifl) / sizeof(struct iflist); getiflist(ifl, &ifl_len); } /** * Set up log file and run in the backgroud */ void daemonize(void) { showtime = 1; init_log_mux = 0; #ifdef WINDOWS init_log(debug); if (!debug) { FILE *pidfh; if (strcmp(pidfile, "")) { // Write out the pid file, before we redirect STDERR to the log. if ((pidfh = fopen(pidfile, "w")) == NULL) { syserror(0, 0, "Can't open pid file for writing"); exit(1); } fprintf(pidfh, "%d\n", GetCurrentProcessId()); fclose(pidfh); } } if (!SetPriorityClass(GetCurrentProcess(), get_win_priority(priority))) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); log0(0, 0, "Error setting priority (%d): %s", GetLastError(), errbuf); } SetConsoleCtrlHandler(winsig, TRUE); #else // WINDOWS if (!debug) { int pid, fd; FILE *pidfh; if ((pid = fork()) == -1) { perror("Couldn't fork"); exit(1); } else if (pid > 0) { parent = 1; exit(0); } setsid(); for (fd = 0; fd < 30; fd++) { if ((fd != 2) && (fd != listener)) { close(fd); } } #ifdef VMS chdir("SYS$LOGIN"); #else chdir("/"); #endif umask(0); init_log(debug); if (strcmp(pidfile, "")) { // Write out the pid file, before we redirect STDERR to the log. if ((pidfh = fopen(pidfile, "w")) == NULL) { perror("Can't open pid file for writing"); exit(1); } fprintf(pidfh, "%d\n", getpid()); fclose(pidfh); } } if (nice(priority) == -1) { syserror(0, 0, "Error setting priority"); } { struct sigaction act; sigfillset(&act.sa_mask); act.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; act.sa_handler = gotsig; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); act.sa_handler = gotpipe; sigaction(SIGPIPE, &act, NULL); act.sa_handler = SIG_IGN; sigaction(SIGCHLD, &act, NULL); } #endif // WINDOWS } /** * Initialize crypto library, generate keys */ void key_init(void) { #ifndef NO_ENCRYPTION char *keyname; int size, i; uint8_t curve; crypto_init(sys_keys); if ((keyfile_count == 0) && (keyinfo_count == 0)) { privkey[0].rsa = gen_RSA_key(0, RSA_EXP, NULL); if (!privkey[0].key) { exit(1); } privkey_type[0] = KEYBLOB_RSA; key_count = 1; } else if (keyinfo_count != 0) { key_count = 0; for (i = 0; i < keyinfo_count; i++) { if (keyfile_count <= i) { keyname = NULL; } else { keyname = keyfile[i]; } if (!strncmp(keyinfo[i], "ec:", 3)) { curve = get_curve(&keyinfo[i][3]); if (curve == 0) { log0(0, 0, "Invalid EC curve: %s", &keyinfo[i][3]); exit(1); } privkey[key_count].ec = gen_EC_key(curve, 0, keyname); privkey_type[key_count] = KEYBLOB_EC; if (!privkey[key_count].key) { exit(1); } } else if (!strncmp(keyinfo[i], "rsa:", 4)) { size = atoi(&keyinfo[i][4]); if ((size < 512) || (size > 2048)) { log0(0, 0, "Invalid RSA key size: %s", &keyinfo[i][4]); exit(1); } privkey[key_count].rsa = gen_RSA_key(size, RSA_EXP, keyname); privkey_type[key_count] = KEYBLOB_RSA; if (!privkey[key_count].key) { exit(1); } } else { log0(0, 0, "Invalid keyinfo entry: %s", keyinfo[i]); exit(1); } key_count++; } } else { for (i = 0; i < keyfile_count; i++) { privkey[key_count] = read_private_key(keyfile[i], &privkey_type[key_count]); if (privkey_type[key_count] == 0) { exit(1); } key_count++; } } if ((proxy_type == RESPONSE_PROXY) && (ecdh_curve != 0)) { dhkey.ec = gen_EC_key(ecdh_curve, 1, NULL); if (!dhkey.key) { log0(0, 0, "Failed to generate DH key"); exit(1); } } #endif } /** * Do all socket creation and initialization */ void create_sockets(void) { struct addrinfo ai_hints, *ai_rval; int family, found_if, rval, fdflag, i; if (!strcmp(out_if.name, "")) { for (i = 0, found_if = 0; (i < ifl_len) && !found_if; i++) { if ((ifl[i].su.ss.ss_family == AF_INET) && (!ifl[i].isloopback)) { found_if = 1; out_if = ifl[i]; } } if (!found_if) { for (i = 0, found_if = 0; (i < ifl_len) && !found_if; i++) { if (ifl[i].su.ss.ss_family == AF_INET) { found_if = 1; out_if = ifl[i]; } } } if (!found_if) { log0(0, 0, "ERROR: no network interface found for family"); exit(1); } } if (out_if.su.ss.ss_family == AF_INET6) { out_if.su.sin6.sin6_port = htons(port); } else { out_if.su.sin.sin_port = htons(port); } if (proxy_type == CLIENT_PROXY) { family = out_if.su.ss.ss_family; for (i = 0; i < hbhost_count; i++) { if (hb_hosts[i].ss.ss_family == AF_INET6) { family = AF_INET6; break; } } } else { family = AF_INET; for (i = 0; i < pub_multi_count; i++) { if (pub_multi[i].ss.ss_family == AF_INET6) { family = AF_INET6; break; } } if ((proxy_type == SERVER_PROXY) && (down_addr.ss.ss_family == AF_INET6)) { family = AF_INET6; } } if ((listener = socket(family, SOCK_DGRAM, 0)) == INVALID_SOCKET) { sockerror(0, 0, "Error creating socket for listener"); exit(1); } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = family; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; if ((rval = getaddrinfo(NULL, portname, &ai_hints, &ai_rval)) != 0) { log0(0, 0, "Error getting bind address: %s", gai_strerror(rval)); exit(1); } if (bind(listener, ai_rval->ai_addr, ai_rval->ai_addrlen) == SOCKET_ERROR) { sockerror(0, 0, "Error binding socket for listener"); closesocket(listener); exit(1); } freeaddrinfo(ai_rval); #ifndef BLOCKING #ifdef WINDOWS fdflag = 1; if (ioctlsocket(listener, FIONBIO, &fdflag) == SOCKET_ERROR) { sockerror(0, 0, "Error setting non-blocking option"); closesocket(listener); exit(1); } #else if ((fdflag = fcntl(listener, F_GETFL)) == SOCKET_ERROR) { sockerror(0, 0, "Error getting socket descriptor flags"); closesocket(listener); exit(1); } fdflag |= O_NONBLOCK; if (fcntl(listener, F_SETFL, fdflag) == SOCKET_ERROR) { sockerror(0, 0, "Error setting non-blocking option"); closesocket(listener); exit(1); } #endif #endif // BLOCKING if (family == AF_INET6) { #if defined IPV6_TCLASS && !defined WINDOWS if (setsockopt(listener, IPPROTO_IPV6, IPV6_TCLASS, (char *)&dscp, sizeof(dscp)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting dscp"); closesocket(listener); exit(1); } #endif if (setsockopt(listener, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, sizeof(ttl)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting ttl"); closesocket(listener); exit(1); } #ifdef IPV6_MTU_DISCOVER { int mtuflag = IP_PMTUDISC_DONT; if (setsockopt(listener, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (char *)&mtuflag, sizeof(mtuflag)) == SOCKET_ERROR) { sockerror(0, 0, "Error disabling MTU discovery"); closesocket(listener); exit(1); } } #endif if (setsockopt(listener, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&out_if.ifidx, sizeof(int)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting outgoing interface"); closesocket(listener); exit(1); } } else { if (setsockopt(listener, IPPROTO_IP, IP_MULTICAST_IF, (char *)&out_if.su.sin.sin_addr, sizeof(out_if.su.sin.sin_addr)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting outgoing interface"); closesocket(listener); exit(1); } } #if (defined WINDOWS && _WIN32_WINNT < _WIN32_WINNT_LONGHORN) ||\ (defined NO_DUAL) if (family == AF_INET) { #endif if (setsockopt(listener, IPPROTO_IP, IP_TOS, (char *)&dscp, sizeof(dscp)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting dscp"); closesocket(listener); exit(1); } #ifdef IP_MTU_DISCOVER { int mtuflag = IP_PMTUDISC_DONT; if (setsockopt(listener, IPPROTO_IP, IP_MTU_DISCOVER, (char *)&mtuflag, sizeof(mtuflag)) == SOCKET_ERROR) { sockerror(0, 0, "Error disabling MTU discovery"); closesocket(listener); exit(1); } } #endif if (setsockopt(listener, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting ttl"); closesocket(listener); exit(1); } #if (defined WINDOWS && _WIN32_WINNT < _WIN32_WINNT_LONGHORN) ||\ (defined NO_DUAL) } #endif if (rcvbuf) { if (setsockopt(listener, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting receive buffer size"); exit(1); } if (setsockopt(listener, SOL_SOCKET, SO_SNDBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting send buffer size"); exit(1); } } else { rcvbuf = DEF_RCVBUF; if (setsockopt(listener, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { rcvbuf = DEF_BSD_RCVBUF; if (setsockopt(listener, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting receive buffer size"); exit(1); } } rcvbuf = DEF_RCVBUF; if (setsockopt(listener, SOL_SOCKET, SO_SNDBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { rcvbuf = DEF_BSD_RCVBUF; if (setsockopt(listener, SOL_SOCKET, SO_SNDBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting send buffer size"); exit(1); } } } for (i = 0; i < pub_multi_count; i++) { if (!multicast_join(listener, 0, &pub_multi[i], m_interface, interface_count, server_fp, server_fp_count)) { exit(1); } } } /** * Initialization based on command line args */ void initialize(void) { char hostname[256]; struct addrinfo ai_hints, *ai_rval; int rval, i; parent = 0; // Load list of multicast interfaces if (interface_count == 0) { for (i = 0; i < ifl_len; i++) { if (!ifl[i].isloopback) { m_interface[interface_count++] = ifl[i]; } } } // No non-loopback interfaces, so just use the hostname's interface if (interface_count == 0) { gethostname(hostname, sizeof(hostname)); memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(hostname, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Can't get address of hostname %s: %s\n", hostname, gai_strerror(rval)); exit(1); } memcpy(&m_interface[interface_count].su, ai_rval->ai_addr, ai_rval->ai_addrlen); m_interface[interface_count].ismulti = 1; m_interface[interface_count++].isloopback = 0; freeaddrinfo(ai_rval); } if (!uid) { if (m_interface[0].su.ss.ss_family == AF_INET6) { uid = m_interface[0].su.sin6.sin6_addr.s6_addr[12] << 24; uid |= m_interface[0].su.sin6.sin6_addr.s6_addr[13] << 16; uid |= m_interface[0].su.sin6.sin6_addr.s6_addr[14] << 8; uid |= m_interface[0].su.sin6.sin6_addr.s6_addr[15]; } else { uid = m_interface[0].su.sin.sin_addr.s_addr; } } if (proxy_type == CLIENT_PROXY) { pub_multi_count = 0; } else if (!pub_multi_count) { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = AI_NUMERICHOST; if ((rval = getaddrinfo(DEF_PUB_MULTI, out_portname, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Can't get address of default public address: %s\n", gai_strerror(rval)); exit(1); } memcpy(&pub_multi[0], ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); pub_multi_count = 1; } for (i = 0; i < MAXLIST; i++) { group_list[i].group_id = 0; } next_hb_time.tv_sec = 0; next_hb_time.tv_usec = 0; last_key_req.tv_sec = 0; last_key_req.tv_usec = 0; atexit(cleanup); key_init(); create_sockets(); daemonize(); showtime = 1; } uftp-4.1.5/client_init.c0000644000076400007640000004665412250244021014157 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include #include "win_func.h" #else // WINDOWS #include #include #include #include #include #endif #include "client.h" #include "client_init.h" #include "client_config.h" #include "client_common.h" static int parent; // Is the the parent process that exits after a fork? /** * Cleanup routine set up by atexit */ void cleanup(void) { int i; for (i = 0; i < MAXLIST; i++) { if (group_list[i].group_id != 0) { file_cleanup(&group_list[i], 1); } } if (!parent) { for (i = 0; i < pub_multi_count; i++) { if (server_count > 0) { multicast_leave(listener, 0, &pub_multi[i], m_interface, interface_count, server_keys, server_count); if (has_proxy) { multicast_leave(listener, 0, &pub_multi[i], m_interface, interface_count, &proxy_info, 1); } } else { multicast_leave(listener, 0, &pub_multi[i], m_interface, interface_count, NULL, 0); } } } closesocket(listener); for (i = 0; i < key_count; i++) { if (privkey_type[i] == KEYBLOB_RSA) { free_RSA_key(privkey[i].rsa); } else { free_EC_key(privkey[i].ec); } } crypto_cleanup(); #ifdef WINDOWS WSACleanup(); #endif close_log(); } /** * Generic signal handler, exits on signal */ void gotsig(int sig) { log0(0, 0, "Exiting on signal %d", sig); exit(0); } /** * Signal handler for SIGPIPE */ void gotpipe(int sig) { log0(0, 0, "Got SIGPIPE"); } #ifdef WINDOWS /** * Windows event handler, exits */ BOOL WINAPI winsig(DWORD event) { switch (event) { case CTRL_C_EVENT: log0(0, 0, "Got CTRL_C_EVENT"); break; case CTRL_BREAK_EVENT: log0(0, 0, "Got CTRL_BREAK_EVENT"); break; case CTRL_CLOSE_EVENT: log0(0, 0, "Got CTRL_CLOSE_EVENT"); break; case CTRL_LOGOFF_EVENT: log0(0, 0, "Got CTRL_LOGOFF_EVENT"); break; case CTRL_SHUTDOWN_EVENT: log0(0, 0, "Got CTRL_SHUTDOWN_EVENT"); break; default: log0(0, 0, "GOT unknown event", event); break; } exit(0); } #endif /** * Do initial setup before parsing arguments, including getting interface list */ void pre_initialize(void) { #ifdef WINDOWS struct WSAData data; if (WSAStartup(2, &data)) { fprintf(stderr, "Error in WSAStartup: %d\n", WSAGetLastError()); exit(1); } #endif applog = stderr; ifl_len = sizeof(ifl) / sizeof(struct iflist); getiflist(ifl, &ifl_len); } /** * Set up log file and run in the backgroud */ void daemonize(void) { showtime = 1; init_log_mux = 0; #ifdef WINDOWS init_log(debug); if (!debug) { FILE *pidfh; if (strcmp(pidfile, "")) { // Write out the pid file, before we redirect STDERR to the log. if ((pidfh = fopen(pidfile, "w")) == NULL) { syserror(0, 0, "Can't open pid file for writing"); exit(1); } fprintf(pidfh, "%d\n", GetCurrentProcessId()); fclose(pidfh); } } if (!SetPriorityClass(GetCurrentProcess(), get_win_priority(priority))) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); log0(0, 0, "Error setting priority (%d): %s", GetLastError(), errbuf); } SetConsoleCtrlHandler(winsig, TRUE); #else // WINDOWS if (!debug) { int pid, fd; FILE *pidfh; if ((pid = fork()) == -1) { perror("Couldn't fork"); exit(1); } else if (pid > 0) { parent = 1; exit(0); } setsid(); for (fd = 0; fd < 30; fd++) { if ((fd != 2) && (fd != listener)) { close(fd); } } #ifdef VMS chdir("SYS$LOGIN"); #else chdir("/"); #endif umask(0); init_log(debug); if (strcmp(pidfile, "")) { // Write out the pid file, before we redirect STDERR to the log. if ((pidfh = fopen(pidfile, "w")) == NULL) { syserror(0, 0, "Can't open pid file for writing"); exit(1); } fprintf(pidfh, "%d\n", getpid()); fclose(pidfh); } } if (nice(priority) == -1) { syserror(0, 0, "Error setting priority"); } { struct sigaction act; sigfillset(&act.sa_mask); act.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; act.sa_handler = gotsig; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); act.sa_handler = gotpipe; sigaction(SIGPIPE, &act, NULL); act.sa_handler = SIG_IGN; sigaction(SIGCHLD, &act, NULL); } #endif // WINDOWS } /** * Initialize crypto library, generate keys */ void key_init(void) { #ifndef NO_ENCRYPTION char *keyname; int size, i; uint8_t curve; crypto_init(sys_keys); if ((keyfile_count == 0) && (keyinfo_count == 0)) { privkey[0].rsa = gen_RSA_key(0, RSA_EXP, NULL); if (!privkey[0].key) { exit(1); } privkey_type[0] = KEYBLOB_RSA; key_count = 1; } else if (keyinfo_count != 0) { key_count = 0; for (i = 0; i < keyinfo_count; i++) { if (keyfile_count <= i) { keyname = NULL; } else { keyname = keyfile[i]; } if (!strncmp(keyinfo[i], "ec:", 3)) { curve = get_curve(&keyinfo[i][3]); if (curve == 0) { log0(0, 0, "Invalid EC curve: %s", &keyinfo[i][3]); exit(1); } privkey[key_count].ec = gen_EC_key(curve, 0, keyname); privkey_type[key_count] = KEYBLOB_EC; if (!privkey[key_count].key) { exit(1); } } else if (!strncmp(keyinfo[i], "rsa:", 4)) { size = atoi(&keyinfo[i][4]); if ((size < 512) || (size > 2048)) { log0(0, 0, "Invalid RSA key size: %s", &keyinfo[i][4]); exit(1); } privkey[key_count].rsa = gen_RSA_key(size, RSA_EXP, keyname); privkey_type[key_count] = KEYBLOB_RSA; if (!privkey[key_count].key) { exit(1); } } else { log0(0, 0, "Invalid keyinfo entry: %s", keyinfo[i]); exit(1); } key_count++; } } else { for (i = 0; i < keyfile_count; i++) { privkey[key_count] = read_private_key(keyfile[i], &privkey_type[key_count]); if (privkey_type[key_count] == 0) { exit(1); } key_count++; } } #endif } /** * Do all socket creation and initialization */ void create_sockets(void) { struct addrinfo ai_hints, *ai_rval; int family, rval, fdflag, i; family = AF_INET; for (i = 0; i < pub_multi_count; i++) { if (pub_multi[i].ss.ss_family == AF_INET6) { family = AF_INET6; break; } } if ((listener = socket(family, SOCK_DGRAM, 0)) == INVALID_SOCKET) { sockerror(0, 0, "Error creating socket for listener"); exit(1); } memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = family; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; if ((rval = getaddrinfo(NULL, portname, &ai_hints, &ai_rval)) != 0) { log0(0, 0, "Error getting bind address: %s", gai_strerror(rval)); exit(1); } if (bind(listener, ai_rval->ai_addr, ai_rval->ai_addrlen) == SOCKET_ERROR) { sockerror(0, 0, "Error binding socket for listener"); closesocket(listener); exit(1); } freeaddrinfo(ai_rval); #ifndef BLOCKING #ifdef WINDOWS fdflag = 1; if (ioctlsocket(listener, FIONBIO, &fdflag) == SOCKET_ERROR) { sockerror(0, 0, "Error setting non-blocking option"); closesocket(listener); exit(1); } #else if ((fdflag = fcntl(listener, F_GETFL)) == SOCKET_ERROR) { sockerror(0, 0, "Error getting socket descriptor flags"); closesocket(listener); exit(1); } fdflag |= O_NONBLOCK; if (fcntl(listener, F_SETFL, fdflag) == SOCKET_ERROR) { sockerror(0, 0, "Error setting non-blocking option"); closesocket(listener); exit(1); } #endif #endif // BLOCKING if (family == AF_INET6) { #if defined IPV6_TCLASS && !defined WINDOWS if (setsockopt(listener, IPPROTO_IPV6, IPV6_TCLASS, (char *)&dscp, sizeof(dscp)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting dscp"); closesocket(listener); exit(1); } #endif #ifdef IPV6_MTU_DISCOVER { int mtuflag = IP_PMTUDISC_DONT; if (setsockopt(listener, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (char *)&mtuflag, sizeof(mtuflag)) == SOCKET_ERROR) { sockerror(0, 0, "Error disabling MTU discovery"); closesocket(listener); exit(1); } } #endif } #if (defined WINDOWS && _WIN32_WINNT < _WIN32_WINNT_LONGHORN) ||\ (defined NO_DUAL) if (family == AF_INET) { #endif if (setsockopt(listener, IPPROTO_IP, IP_TOS, (char *)&dscp, sizeof(dscp)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting dscp"); closesocket(listener); exit(1); } #ifdef IP_MTU_DISCOVER { int mtuflag = IP_PMTUDISC_DONT; if (setsockopt(listener, IPPROTO_IP, IP_MTU_DISCOVER, (char *)&mtuflag, sizeof(mtuflag)) == SOCKET_ERROR) { sockerror(0, 0, "Error disabling MTU discovery"); closesocket(listener); exit(1); } } #endif #if (defined WINDOWS && _WIN32_WINNT < _WIN32_WINNT_LONGHORN) ||\ (defined NO_DUAL) } #endif if (rcvbuf) { if (setsockopt(listener, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting receive buffer size"); exit(1); } } else { rcvbuf = DEF_RCVBUF; if (setsockopt(listener, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { rcvbuf = DEF_BSD_RCVBUF; if (setsockopt(listener, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) == SOCKET_ERROR) { sockerror(0, 0, "Error setting receive buffer size"); exit(1); } } } for (i = 0; i < pub_multi_count; i++) { if (server_count > 0) { log3(0, 0, "joining ssm for server IPs"); if (!multicast_join(listener, 0, &pub_multi[i], m_interface, interface_count, server_keys, server_count)) { exit(1); } if (has_proxy) { log3(0, 0, "joining ssm for proxy IPs"); if (!multicast_join(listener, 0, &pub_multi[i], m_interface, interface_count, &proxy_info, 1)) { exit(1); } } } else { if (!multicast_join(listener, 0, &pub_multi[i], m_interface, interface_count, NULL, 0)) { exit(1); } } } } /** * Tests to see if files can be moved/renamed between two directories. * Returns 1 on success, 0 on failure */ int dirs_movable(const char *dir1, const char *dir2) { char tempf1[MAXPATHNAME], tempf2[MAXPATHNAME]; int fd; snprintf(tempf1, sizeof(tempf1)-1, "%s%c_uftptmp1", dir1, PATH_SEP); tempf1[sizeof(tempf1)-1] = '\x0'; snprintf(tempf2, sizeof(tempf1)-1, "%s%c_uftptmp2", dir2, PATH_SEP); tempf2[sizeof(tempf2)-1] = '\x0'; if ((fd = open(tempf1, O_WRONLY | O_CREAT, 0644)) < 0) { fprintf(stderr, "couldn't write to directory %s: %s\n", dir1, strerror(errno)); return 0; } close(fd); if ((fd = open(tempf2, O_WRONLY | O_CREAT, 0644)) < 0) { fprintf(stderr, "couldn't write to directory %s: %s\n", dir2, strerror(errno)); return 0; } close(fd); unlink(tempf2); if (rename(tempf1, tempf2) == -1) { fprintf(stderr, "couldn't move between directories %s and %s: %s\n", dir1, dir2, strerror(errno)); unlink(tempf1); return 0; } unlink(tempf1); unlink(tempf2); return 1; } /** * Initialization based on command line args */ void initialize(void) { char tempf1[MAXPATHNAME], hostname[256]; struct addrinfo ai_hints, *ai_rval; int rval, fd, i; parent = 0; srand((unsigned int)time(NULL) ^ getpid()); // Load list of multicast interfaces if (interface_count == 0) { for (i = 0; i < ifl_len; i++) { if (!ifl[i].isloopback) { m_interface[interface_count++] = ifl[i]; } } } // No non-loopback interfaces, so just use the hostname's interface if (interface_count == 0) { gethostname(hostname, sizeof(hostname)); memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = 0; if ((rval = getaddrinfo(hostname, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Can't get address of hostname %s: %s\n", hostname, gai_strerror(rval)); exit(1); } memcpy(&m_interface[interface_count].su, ai_rval->ai_addr, ai_rval->ai_addrlen); m_interface[interface_count].ismulti = 1; m_interface[interface_count++].isloopback = 0; freeaddrinfo(ai_rval); } if (!uid) { if (m_interface[0].su.ss.ss_family == AF_INET6) { uid = m_interface[0].su.sin6.sin6_addr.s6_addr[12] << 24; uid |= m_interface[0].su.sin6.sin6_addr.s6_addr[13] << 16; uid |= m_interface[0].su.sin6.sin6_addr.s6_addr[14] << 8; uid |= m_interface[0].su.sin6.sin6_addr.s6_addr[15]; } else { uid = m_interface[0].su.sin.sin_addr.s_addr; } } // Check validity of dest, backup, and temp directories for (i = 0; i < destdircnt; i++) { if (!isfullpath(destdir[i])) { fprintf(stderr, "ERROR: must specify absolute pathname " "for dest directory\n"); exit(1); } snprintf(tempf1, sizeof(tempf1)-1, "%s%c_uftptmp1",destdir[i],PATH_SEP); tempf1[sizeof(tempf1)-1] = '\x0'; if ((fd = open(tempf1, O_WRONLY | O_CREAT, 0644)) < 0) { perror("couldn't write to dest directory"); exit(1); } close(fd); unlink(tempf1); if (backupcnt > 0) { // backupcnt and destdircnt are always equal if (!strcmp(backupdir[i], destdir[i])) { fprintf(stderr, "ERROR: corresponding backup dir and dest dir " "must be different\n"); exit(1); } if (!isfullpath(backupdir[i])) { fprintf(stderr, "ERROR: must specify absolute pathname " "for backup directory\n"); exit(1); } if (!dirs_movable(destdir[i], backupdir[i])) { exit(1); } } } if (strcmp(tempdir, "")) { if (destdircnt > 1) { fprintf(stderr, "ERROR: Cannot use a temp directory " "with multiple dest directories\n"); exit(1); } if (backupcnt > 0) { fprintf(stderr, "ERROR: Cannot use a temp directory " "with a backup directory\n"); exit(1); } if (!strcmp(tempdir, destdir[0])) { fprintf(stderr, "ERROR: temp dir and dest dir must be different\n"); exit(1); } if (!isfullpath(tempdir)) { fprintf(stderr, "ERROR: must specify absolute pathname " "for temp directory\n"); exit(1); } if (!dirs_movable(tempdir, destdir[0])) { exit(1); } } if (strcmp(postreceive, "")) { if (!isfullpath(postreceive)) { fprintf(stderr, "ERROR: must specify absolute pathname " "for postreceive script\n"); exit(1); } } if (!pub_multi_count) { memset(&ai_hints, 0, sizeof(ai_hints)); ai_hints.ai_family = AF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; ai_hints.ai_protocol = 0; ai_hints.ai_flags = AI_NUMERICHOST; if ((rval = getaddrinfo(DEF_PUB_MULTI, NULL, &ai_hints, &ai_rval)) != 0) { fprintf(stderr, "Can't get address of default public address: %s\n", gai_strerror(rval)); exit(1); } memcpy(&pub_multi[0], ai_rval->ai_addr, ai_rval->ai_addrlen); freeaddrinfo(ai_rval); pub_multi_count = 1; } for (i = 0; i < MAXLIST; i++) { group_list[i].group_id = 0; } next_hb_time.tv_sec = 0; next_hb_time.tv_usec = 0; next_keyreq_time.tv_sec = 0; next_keyreq_time.tv_usec = 0; atexit(cleanup); key_init(); create_sockets(); daemonize(); showtime = 1; } uftp-4.1.5/proxy_main.c0000644000076400007640000000303612137013343014033 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2010 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include "proxy_config.h" #include "proxy_init.h" #include "proxy_loop.h" int main(int argc, char *argv[]) { pre_initialize(); process_args(argc, argv); initialize(); mainloop(); return 0; } uftp-4.1.5/client_loop.c0000644000076400007640000004135212250244021014153 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #ifdef WINDOWS #include "win_func.h" #else // WINDOWS #include #include #include #include #include #endif #include "client.h" #include "client_common.h" #include "client_loop.h" #include "client_announce.h" #include "client_fileinfo.h" #include "client_transfer.h" #include "heartbeat_send.h" /** * 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: log1(group->group_id, group->file_id, "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)) { log5(0, 0, "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); log3(group->group_id, group->file_id, "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)) { log1(group->group_id, group->file_id, "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))) { log5(0, 0, "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))) { log5(0, 0, "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; uint8_t version, *func; uint16_t txseq; union sockaddr_u src; struct timeval *tv, rxtime; double new_grtt; log0(0, 0, "%s", VERSIONSTR); for (i = 0; i < key_count; i++) { if (privkey_type[i] == KEYBLOB_RSA) { log1(0, 0, "Loaded %d bit RSA key with fingerprint %s", RSA_keylen(privkey[i].rsa) * 8, print_key_fingerprint(privkey[i], KEYBLOB_RSA)); } else { log1(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, "read timeout: %d.%06d", tv->tv_sec, tv->tv_usec); } if (read_packet(listener, &src, buf, &packetlen, MAXMTU, tv) <= 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, "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, "Invalid message from %s: not uftp packet " "or invalid version", rxname); continue; } if (packetlen < sizeof(struct uftp_h) + 4) { log1(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) { log3(group->group_id, 0, "seq out of range, dropping"); continue; } if (group->cc_type != CC_NONE) { update_loss_history(group, txseq, packetlen); } 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) { log1(group->group_id, 0, "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)) { log1(group->group_id, 0, "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)))) { log1(group->group_id, 0, "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); log5(group->group_id, 0, "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) { log1(group->group_id, group->file_id, "Version mismatch"); continue; } if (group->src_id != header->src_id) { log1(group->group_id, group->file_id, "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 { log1(group->group_id, group->file_id, "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 { log1(group->group_id, group->file_id, "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; } } } } uftp-4.1.5/heartbeat_send.h0000644000076400007640000000377412137013343014634 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _HEARTBEAT_SEND_H #define _HEARTBEAT_SEND_H void handle_hb_response(SOCKET s, const union sockaddr_u *src, const unsigned char *message, unsigned meslen, union sockaddr_u hb_hosts[], int num_hosts, union key_t privkey, int keytype, uint32_t uid); void send_auth_hb_request(SOCKET s, union sockaddr_u *hbhost, uint32_t nonce, union key_t privkey, int keytype, uint32_t uid); void send_hb_request(SOCKET s, union sockaddr_u hb_hosts[], int num_hosts, struct timeval *next_hb_time, int hb_interval, uint32_t uid); #endif // _HEARTBEAT_SEND_H uftp-4.1.5/win_func.h0000644000076400007640000000301312137013343013456 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2011 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ extern int optind; extern char *optarg; char getopt(int argc, char *argv[], const char options[]); int gettimeofday(struct timeval *tv, struct timezone *tz); int get_win_priority(int priority);uftp-4.1.5/client_fileinfo.c0000664000076400007640000005177412250244021015010 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include #include "win_func.h" #else // if WINDOWS #include #endif #include "client.h" #include "client_common.h" #include "client_fileinfo.h" #include "client_transfer.h" /** * Send a COMPLETE with the given status in reponse to a FILEINFO, * set the phase to MIDGROUP, and reset the timeout */ void early_complete(struct group_list_t *group, int status, int freespace) { group->phase = PHASE_MIDGROUP; group->fileinfo.comp_status = status; send_complete(group, freespace); set_timeout(group, 0); } /** * 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))) { log1(group->group_id, group->file_id, "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) { log1(group->group_id, ntohs(fileinfo->file_id), "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] == '\\'))) { log1(group->group_id, ntohs(fileinfo->file_id), "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) { log1(group->group_id, group->file_id, "Rejecting FILEINFO from server: blank link name"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } } return 1; } /** * 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, "")) { log0(group->group_id, group->file_id, "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])) { log0(group->group_id, group->file_id, "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) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "Rejecting file: max pathname length exceeded"); early_complete(group, COMP_STAT_REJECTED, 0); return 0; } return 1; } /** * Perform FILEINFO processing specific to a regular file in restart mode * Returns 1 if a COMPLETE was sent in response, 0 otherwise */ int handle_fileinfo_restart(struct group_list_t *group) { stat_struct statbuf; if ((!strcmp(group->fileinfo.name, group->restartinfo->name)) && (group->fileinfo.size == group->restartinfo->size) && (group->fileinfo.blocks == group->restartinfo->blocks) && (group->fileinfo.sections == group->restartinfo->sections)) { // Flag this file to restart a failed transfer group->fileinfo.restart = 1; return 0; } else if ((lstat_func(group->fileinfo.filepath, &statbuf) != -1) && S_ISREG(statbuf.st_mode) && (statbuf.st_size == group->fileinfo.size)) { // This file was finished on the last attempt, // so respond with a COMPLETE right away early_complete(group, COMP_STAT_NORMAL, 0); return 1; } return 0; } /** * Perform FILEINFO processing specific to a regular file in sync mode * Returns 1 if a COMPLETE was sent in response, 0 otherwise */ int handle_fileinfo_sync(struct group_list_t *group) { stat_struct statbuf; if (lstat_func(group->fileinfo.filepath, &statbuf) != -1) { // If source is newer, skip // If source is older, overwrite // If timstamps same, skip if sizes are also same int skip; if (group->fileinfo.tstamp < statbuf.st_mtime) { skip = 1; } else if (group->fileinfo.tstamp > statbuf.st_mtime) { skip = 0; } else if (S_ISREG(statbuf.st_mode) && (statbuf.st_size == group->fileinfo.size)) { skip = 1; } else { skip = 0; } if (skip) { if (log_level >= 1) { log1(group->group_id, group->file_id, "skipping file, in sync"); } else { log0(group->group_id, group->file_id, "Skip %s", group->fileinfo.name); } early_complete(group, COMP_STAT_SKIPPED, 0); return 1; } else { if (log_level >= 1) { log1(group->group_id, group->file_id, "overwriting out of sync file"); } else { log0(group->group_id, group->file_id, "Overwrite %s", group->fileinfo.name); } group->fileinfo.comp_status = COMP_STAT_OVERWRITE; if (group->sync_preview) { log1(group->group_id, group->file_id, "Sync preview mode, skipping receive"); early_complete(group, COMP_STAT_OVERWRITE, 0); return 1; } if (!tempfile) { move_to_backup(group); } } } else { if (log_level >= 1) { log1(group->group_id, group->file_id, "copying new file"); } else { log0(group->group_id, group->file_id, "Copy %s", group->fileinfo.name); } if (group->sync_preview) { log1(group->group_id, group->file_id, "Sync preview mode, skipping receive"); early_complete(group, COMP_STAT_NORMAL, 0); return 1; } if (!tempfile) { move_to_backup(group); } } return 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) { syserror(group->group_id, group->file_id, "Error opening data file"); early_complete(group, COMP_STAT_REJECTED, 0); return; } if (group->fileinfo.size > free_space(group->fileinfo.filepath)) { log0(group->group_id,group->file_id, "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->phase = PHASE_RECEIVING; send_fileinfo_ack(group, group->fileinfo.restart); set_timeout(group, 0); } /** * Perform FILEINFO processing specific to an empty directory */ void handle_fileinfo_dir(struct group_list_t *group, int found_dir) { if (!found_dir && !group->sync_preview) { log2(group->group_id, group->file_id, "Creating directory"); if (mkdir(group->fileinfo.filepath, 0755) == -1) { syserror(group->group_id, group->file_id, "Failed to create directory %s", group->fileinfo.filepath); early_complete(group, COMP_STAT_REJECTED, 0); return; } } early_complete(group, found_dir ? COMP_STAT_SKIPPED : COMP_STAT_NORMAL, 0); } /** * Perform FILEINFO processing specific to a symbolic link */ void handle_fileinfo_link(struct group_list_t *group) { #ifndef WINDOWS if (!group->sync_preview) { if (symlink(group->fileinfo.linkname, group->fileinfo.filepath) == -1) { syserror(group->group_id, group->file_id, "Failed to create symlink %s", group->fileinfo.filepath); early_complete(group, COMP_STAT_REJECTED, 0); return; } } #endif early_complete(group, COMP_STAT_NORMAL, 0); } /** * Perform FILEINFO processing specific to a delete command */ void handle_fileinfo_delete(struct group_list_t *group) { if (!group->sync_preview) { move_to_backup(group); } early_complete(group, COMP_STAT_NORMAL, 0); } /** * Perform FILEINFO processing specific to a freespace command */ void handle_fileinfo_freespace(struct group_list_t *group) { early_complete(group, COMP_STAT_NORMAL, 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; } if (!group->sync_mode || (log_level >= 1)) { log0(group->group_id, group->file_id, "Name of file to receive: %s", group->fileinfo.name); } switch (group->fileinfo.ftype) { case FTYPE_REG: log1(group->group_id, group->file_id, "Bytes: %s, Blocks: %d, Sections: %d", printll(group->fileinfo.size), group->fileinfo.blocks, group->fileinfo.sections); log3(group->group_id, group->file_id, "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: log1(group->group_id, group->file_id, "Empty directory"); break; case FTYPE_LINK: log1(group->group_id, group->file_id, "Symbolic link to %s", group->fileinfo.linkname); break; case FTYPE_DELETE: log1(group->group_id, group->file_id, "Deleting file/directory"); break; case FTYPE_FREESPACE: log1(group->group_id, group->file_id, "Get free space for path"); break; default: log1(group->group_id, group->file_id, "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)) { log0(group->group_id, group->file_id, "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) { log3(group->group_id, group->file_id, "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 log3(group->group_id, group->file_id, "calling move_to_backup"); if (!tempfile) { move_to_backup(group); } } } else { log3(group->group_id, group->file_id, "found dir"); found_dir = 1; } } else if (errno != ENOENT) { syserror(group->group_id, group->file_id, "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: log0(group->group_id, group->file_id, "Error handling FILEINFO: shouldn't get here!"); } } uftp-4.1.5/uftp_common.c0000644000076400007640000016677112250244021014207 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #include #include #include #ifdef WINDOWS #include #include #include #include #include "uftp.h" #include "uftp_common.h" #include "encryption.h" #include "win_func.h" void getiflist(struct iflist *list, int *len) { IP_ADAPTER_ADDRESSES *head, *curr; IP_ADAPTER_UNICAST_ADDRESS *uni; char *buf; int buflen, err, i; buflen = 100000; buf = safe_calloc(buflen, 1); head = (IP_ADAPTER_ADDRESSES *)buf; if ((err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, head, &buflen)) != ERROR_SUCCESS) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, errbuf, sizeof(errbuf), NULL); log0(0, 0, "GetAdaptersAddresses failed: (%d) %s", err, errbuf); free(buf); return; } for (*len = 0, curr = head; curr; curr = curr->Next) { if (curr->IfType == IF_TYPE_TUNNEL) continue; for (uni = curr->FirstUnicastAddress; uni; uni = uni->Next) { if (curr->OperStatus == IfOperStatusUp) { memset(&list[*len], 0, sizeof(struct iflist)); strncpy(list[*len].name, (char *)curr->AdapterName, sizeof(list[i].name) - 1); memcpy(&list[*len].su, uni->Address.lpSockaddr, uni->Address.iSockaddrLength); list[*len].isloopback = (curr->IfType == IF_TYPE_SOFTWARE_LOOPBACK); list[*len].ismulti = ((curr->Flags & IP_ADAPTER_NO_MULTICAST) == 0); if (uni->Address.lpSockaddr->sa_family == AF_INET6) { list[*len].ifidx = curr->Ipv6IfIndex; } else { list[*len].ifidx = curr->IfIndex; } (*len)++; } } } free(buf); } #else /*if WINDOWS*/ #include #include #include #include #include #include #include #include #include #include "uftp.h" #include "uftp_common.h" #include "encryption.h" #ifdef HAS_GETIFADDRS #include void getiflist(struct iflist *list, int *len) { struct ifaddrs *ifa, *ifa_tmp; int count; unsigned ifidx; if (getifaddrs(&ifa) == -1) { syserror(0, 0, "getifaddrs failed"); *len = 0; return; } ifa_tmp = ifa; count = *len; *len = 0; while (ifa_tmp && (*len < count)) { if ((ifidx = if_nametoindex(ifa_tmp->ifa_name)) == 0) { syserror(0, 0, "Error getting interface index for interface %s", ifa_tmp->ifa_name); continue; } if (ifa_tmp->ifa_addr && ((ifa_tmp->ifa_addr->sa_family == AF_INET) || (ifa_tmp->ifa_addr->sa_family == AF_INET6)) && ((ifa_tmp->ifa_flags & IFF_UP) != 0)) { memset(&list[*len], 0, sizeof(struct iflist)); strncpy(list[*len].name, ifa_tmp->ifa_name, sizeof(list[*len].name) - 1); memcpy(&list[*len].su, ifa_tmp->ifa_addr, sizeof(struct sockaddr_storage)); list[*len].isloopback = (ifa_tmp->ifa_flags & IFF_LOOPBACK) != 0; list[*len].ismulti = (ifa_tmp->ifa_flags & IFF_MULTICAST) != 0; list[*len].ifidx = ifidx; (*len)++; } ifa_tmp = ifa_tmp->ifa_next; } freeifaddrs(ifa); } #else void getiflist(struct iflist *list, int *len) { int s, i, count; struct lifconf ifc; struct lifreq *ifr, ifr_tmp_flags, ifr_tmp_ifidx; if (*len <= 0) return; count = *len; ifr = safe_malloc(sizeof(struct lifreq) * count); ifc.lifc_family = AF_UNSPEC; ifc.lifc_flags = 0; ifc.lifc_len = sizeof(struct lifreq) * count; ifc.lifc_req = ifr; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { sockerror(0, 0, "Error creating socket for interface list"); free(ifr); *len = 0; return; } if (ioctl(s, SIOCGLIFCONF, &ifc) == -1) { syserror(0, 0, "Error getting interface list"); free(ifr); close(s); *len = 0; return; } count = ifc.lifc_len / sizeof(struct lifreq); for (i = 0, *len = 0; i < count; i++) { strcpy(ifr_tmp_flags.lifr_name, ifr[i].lifr_name); if (ioctl(s, SIOCGLIFFLAGS, &ifr_tmp_flags) == -1) { syserror(0, 0, "Error getting flags for interface %s", ifr[i].lifr_name); continue; } strcpy(ifr_tmp_ifidx.lifr_name, ifr[i].lifr_name); if (ioctl(s, SIOCGLIFINDEX, &ifr_tmp_ifidx) == -1) { syserror(0, 0, "Error getting interface index for interface %s", ifr[i].lifr_name); continue; } if (((ifr[i].lifr_addr.ss_family == AF_INET) || (ifr[i].lifr_addr.ss_family == AF_INET6)) && ((ifr_tmp_flags.lifr_flags & IFF_UP) != 0)) { memset(&list[*len], 0, sizeof(struct iflist)); strncpy(list[*len].name,ifr[i].lifr_name, sizeof(list[i].name) - 1); memcpy(&list[*len].su, &ifr[i].lifr_addr, sizeof(struct sockaddr_storage)); list[*len].isloopback = (ifr_tmp_flags.lifr_flags & IFF_LOOPBACK) != 0; list[*len].ismulti = (ifr_tmp_flags.lifr_flags & IFF_MULTICAST)!=0; list[*len].ifidx = ifr_tmp_ifidx.lifr_index; (*len)++; } } free(ifr); close(s); } #endif /*if Sun*/ #ifdef VMS pid_t GENERIC_SETSID(void) { return(0); } #endif #endif /*if WINDOWS*/ const char *printll(int64_t n) { static char str[50]; #ifdef WINDOWS snprintf(str, sizeof(str) - 1, "%I64d", n); #else snprintf(str, sizeof(str) - 1, "%lld", (long long)n); #endif str[sizeof(str) - 1] = '\x0'; return &str[0]; } int32_t diff_sec(struct timeval t2, struct timeval t1) { return t2.tv_sec - t1.tv_sec; } int64_t diff_usec(struct timeval t2, struct timeval t1) { return (t2.tv_usec - t1.tv_usec) + (int64_t)1000000 * (t2.tv_sec - t1.tv_sec); } int cmptimestamp(struct timeval t1, struct timeval t2) { if (t1.tv_sec > t2.tv_sec) { return 1; } else if (t1.tv_sec < t2.tv_sec) { return -1; } else if (t1.tv_usec > t2.tv_usec) { return 1; } else if (t1.tv_usec < t2.tv_usec) { return -1; } else { return 0; } } struct timeval add_timeval(struct timeval t2, struct timeval t1) { struct timeval result; result.tv_sec = t2.tv_sec + t1.tv_sec; result.tv_usec = t2.tv_usec + t1.tv_usec; while (result.tv_usec >= 1000000) { result.tv_usec -= 1000000; result.tv_sec++; } return result; } void add_timeval_d(struct timeval *t2, double t1) { t2->tv_sec += (long)(floor(t1) + 0); t2->tv_usec += (long)((t1 - floor(t1)) * 1000000); while (t2->tv_usec >= 1000000) { t2->tv_usec -= 1000000; t2->tv_sec++; } } struct timeval diff_timeval(struct timeval t2, struct timeval t1) { struct timeval result; result.tv_sec = t2.tv_sec - t1.tv_sec; result.tv_usec = t2.tv_usec - t1.tv_usec; while (result.tv_usec < 0) { result.tv_usec += 1000000; result.tv_sec--; } return result; } /** * Gets the name of the UFTP message type for the given message type constant */ const char *func_name(int func) { switch (func) { case ANNOUNCE: return "ANNOUNCE"; case REGISTER: return "REGISTER"; case CLIENT_KEY: return "CLIENT_KEY"; case REG_CONF: return "REG_CONF"; case KEYINFO: return "KEYINFO"; case KEYINFO_ACK: return "KEYINFO_ACK"; case FILEINFO: return "FILEINFO"; case FILEINFO_ACK: return "FILEINFO_ACK"; case FILESEG: return "FILESEG"; case DONE: return "DONE"; case STATUS: return "STATUS"; case COMPLETE: return "COMPLETE"; case DONE_CONF: return "DONE_CONF"; case HB_REQ: return "HB_REQ"; case HB_RESP: return "HB_RESP"; case KEY_REQ: return "KEY_REQ"; case PROXY_KEY: return "PROXY_KEY"; case ENCRYPTED: return "ENCRYPTED"; case ABORT: return "ABORT"; case CONG_CTRL: return "CONG_CTRL"; case CC_ACK: return "CC_ACK"; default: return "UNKNOWN"; } } /** * Gets the name of the EC curve for the given EC curve constant. */ const char *curve_name(int curve) { switch (curve) { case CURVE_sect163k1: return "sect163k1"; case CURVE_sect163r1: return "sect163r1"; case CURVE_sect163r2: return "sect163r2"; case CURVE_sect193r1: return "sect193r1"; case CURVE_sect193r2: return "sect193r2"; case CURVE_sect233k1: return "sect233k1"; case CURVE_sect233r1: return "sect233r1"; case CURVE_sect239k1: return "sect239k1"; case CURVE_sect283k1: return "sect283k1"; case CURVE_sect283r1: return "sect283r1"; case CURVE_sect409k1: return "sect409k1"; case CURVE_sect409r1: return "sect409r1"; case CURVE_sect571k1: return "sect571k1"; case CURVE_sect571r1: return "sect571r1"; case CURVE_secp160k1: return "secp160k1"; case CURVE_secp160r1: return "secp160r1"; case CURVE_secp160r2: return "secp160r2"; case CURVE_secp192k1: return "secp192k1"; case CURVE_secp192r1: return "prime192v1"; case CURVE_secp224k1: return "secp224k1"; case CURVE_secp224r1: return "secp224r1"; case CURVE_secp256k1: return "secp256k1"; case CURVE_secp256r1: return "prime256v1"; case CURVE_secp384r1: return "secp384r1"; case CURVE_secp521r1: return "secp521r1"; default: return "UNKNOWN"; } } /** * Gets the EC curve constant for the given curve name. * Returns 0 if the name is invalid */ uint8_t get_curve(const char *name) { if (!strcmp(name, "sect163k1")) { return CURVE_sect163k1; } else if (!strcmp(name, "sect163r1")) { return CURVE_sect163r1; } else if (!strcmp(name, "sect163r2")) { return CURVE_sect163r2; } else if (!strcmp(name, "sect193r1")) { return CURVE_sect193r1; } else if (!strcmp(name, "sect193r2")) { return CURVE_sect193r2; } else if (!strcmp(name, "sect233k1")) { return CURVE_sect233k1; } else if (!strcmp(name, "sect233r1")) { return CURVE_sect233r1; } else if (!strcmp(name, "sect239k1")) { return CURVE_sect239k1; } else if (!strcmp(name, "sect283k1")) { return CURVE_sect283k1; } else if (!strcmp(name, "sect283r1")) { return CURVE_sect283r1; } else if (!strcmp(name, "sect409k1")) { return CURVE_sect409k1; } else if (!strcmp(name, "sect409r1")) { return CURVE_sect409r1; } else if (!strcmp(name, "sect571k1")) { return CURVE_sect571k1; } else if (!strcmp(name, "sect571r1")) { return CURVE_sect571r1; } else if (!strcmp(name, "secp160k1")) { return CURVE_secp160k1; } else if (!strcmp(name, "secp160r1")) { return CURVE_secp160r1; } else if (!strcmp(name, "secp160r2")) { return CURVE_secp160r2; } else if (!strcmp(name, "secp192k1")) { return CURVE_secp192k1; } else if (!strcmp(name, "secp192r1")) { return CURVE_secp192r1; } else if (!strcmp(name, "secp224r1")) { return CURVE_secp224r1; } else if (!strcmp(name, "secp256k1")) { return CURVE_secp256k1; } else if (!strcmp(name, "secp256r1")) { return CURVE_secp256r1; } else if (!strcmp(name, "secp384r1")) { return CURVE_secp384r1; } else if (!strcmp(name, "secp521r1")) { return CURVE_secp521r1; } else if (!strcmp(name, "prime192v1")) { return CURVE_prime192v1; } else if (!strcmp(name, "prime256v1")) { return CURVE_prime256v1; } else { return 0; } } char logfile[MAXPATHNAME]; int showtime; FILE *applog; int log_level, init_log_mux, use_log_mux, max_log_count; f_offset_t log_size, max_log_size; mux_t log_mux; static int rolling = 0; /** * Initialize the log file. */ void init_log(int _debug) { use_log_mux = 0; if (init_log_mux) { if (mux_create(log_mux)) { perror("Failed to create log mutex"); exit(1); } } if (strcmp(logfile, "") && !_debug) { int fd; stat_struct statbuf; if ((lstat_func(logfile, &statbuf) != -1) && S_ISREG(statbuf.st_mode)) { log_size = statbuf.st_size; } else { log_size = 0; } if ((fd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0644)) == -1) { perror("Can't open log file"); exit(1); } dup2(fd, 2); close(fd); showtime = 1; } else { log_size = 0; max_log_size = 0; max_log_count = 0; } applog = stderr; } /** * Close log file */ void close_log() { if (init_log_mux) { mux_destroy(log_mux); } fclose(applog); } /** * Rolls the log file. */ void roll_log() { char oldname[MAXPATHNAME], newname[MAXPATHNAME]; int rval, fd, i; if (rolling) return; rolling = 1; log2(0, 0, "Rolling logs"); for (i = max_log_count; i >=0; i--) { if (i == 0) { rval = snprintf(oldname, sizeof(oldname), "%s", logfile); if (rval >= sizeof(oldname)) { log0(0, 0, "Old log name too long"); rolling = 0; return; } } else { rval = snprintf(oldname, sizeof(oldname), "%s.%d", logfile, i); if (rval >= sizeof(oldname)) { log0(0, 0, "Old log name too long"); rolling = 0; return; } } rval = snprintf(newname, sizeof(newname), "%s.%d", logfile, i + 1); if (rval >= sizeof(oldname)) { log0(0, 0, "New log name too long"); rolling = 0; return; } if (i == max_log_count) { if (unlink(oldname) == -1) { syserror(0, 0, "Couldn't remove log %s", oldname); } } else { if (rename(oldname, newname) == -1) { syserror(0,0, "Couldn't rename log %s to %s", oldname, newname); } } } log2(0, 0, "Opening new log"); if ((fd=open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0644)) == -1) { syserror(0, 0, "Can't open log file"); exit(1); } log2(0, 0, "Switching to new log"); dup2(fd, 2); close(fd); log_size = 0; log2(0, 0, "Switch to new log complete"); rolling = 0; } /** * The main logging function. * Called via a series of macros for a particular log level or output format. */ void logfunc(uint32_t group_id, uint16_t file_id, int level, int _showtime, int newline, int err, int sockerr, const char *str, ...) { struct tm *timeval; struct timeval tv; time_t t; va_list args; int write_len; if (level > log_level) return; if (use_log_mux && !rolling) { if (mux_lock(log_mux)) { write_len = fprintf(applog, "Failed to lock log mutex\n"); if (write_len != -1) log_size += write_len; } } if (_showtime) { gettimeofday(&tv, NULL); // In Windows, tv.tv_sec is long, not time_t t = tv.tv_sec; timeval = localtime(&t); write_len = fprintf(applog, "%04d/%02d/%02d %02d:%02d:%02d.%06d: ", timeval->tm_year + 1900, timeval->tm_mon + 1, timeval->tm_mday, timeval->tm_hour, timeval->tm_min, timeval->tm_sec, (int)tv.tv_usec); if (write_len != -1) log_size += write_len; if (group_id && file_id) { write_len = fprintf(applog, "[%08X:%04X]: ", group_id, file_id); if (write_len != -1) log_size += write_len; } else if (group_id && !file_id) { write_len = fprintf(applog, "[%08X:0]: ", group_id); if (write_len != -1) log_size += write_len; } else if (!group_id && file_id) { write_len = fprintf(applog, "[%04X]: ", file_id); if (write_len != -1) log_size += write_len; } } va_start(args, str); write_len = vfprintf(applog, str, args); if (write_len != -1) log_size += write_len; va_end(args); if (sockerr) { #ifdef WINDOWS char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, WSAGetLastError(), 0, errbuf, sizeof(errbuf), NULL); write_len = fprintf(applog, ": (%d) %s", WSAGetLastError(), errbuf); newline = 0; #else write_len = fprintf(applog, ": %s", strerror(err)); #endif if (write_len != -1) log_size += write_len; } else if (err) { write_len = fprintf(applog, ": %s", strerror(err)); if (write_len != -1) log_size += write_len; } if (newline) { write_len = fprintf(applog, "\n"); if (write_len != -1) log_size += write_len; } fflush(applog); if ((max_log_size > 0) && (log_size > max_log_size)) { roll_log(); } if (use_log_mux && !rolling) { if (mux_unlock(log_mux)) { write_len = fprintf(applog, "Failed to unlock log mutex\n"); if (write_len != -1) log_size += write_len; fflush(applog); } } } /** * Takes a pathname and splits it into a directory part and file part. * The caller is expected to clean up *dir and *file. */ void split_path(const char *path, char **dir, char **file) { #ifdef WINDOWS char *result, *filename; DWORD len, len2; if (strlen(path) == 0) { *dir = NULL; *file = NULL; return; } // GetFullPathNameA doens't handle trailing slashes well, so disallow if ((path[strlen(path)-1] == '/') || (path[strlen(path)-1] == '\\')) { log0(0, 0, "bad path, trailing / or \\ not allowed"); *dir = NULL; *file = NULL; return; } len = GetFullPathNameA(path, 0, NULL, &filename); if (len == 0) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); log0(0, 0, "Error in GetFullPathNameA: %s", errbuf); *dir = NULL; *file = NULL; return; } *dir = NULL; *file = NULL; result = safe_malloc(len); if ((len2 = GetFullPathNameA(path, len, result, &filename)) <= len) { *dir = strdup(result); *file = strdup(filename); if (!*dir || (filename && !*file)) { syserror(0, 0, "strdup failed!"); exit(1); } (*dir)[strlen(*dir) - strlen(*file) - 1] = '\x0'; } free(result); #else char *dirc, *filec; dirc = strdup(path); filec = strdup(path); if (!dirc || !filec) { syserror(0, 0, "strdup failed!"); exit(1); } *dir = strdup(dirname(dirc)); *file = strdup(basename(filec)); if (!*dir || !*file) { syserror(0, 0, "strdup failed!"); exit(1); } free(dirc); free(filec); #endif } /** * Parses a key fingerprint string and saves it to the specified buffer * Returns 1 on success, 0 on fail */ int parse_fingerprint(unsigned char *fingerprint, const char *fingerprint_str) { char *p, *tmp; int num, len; if (fingerprint_str == NULL) { return 0; } tmp = strdup(fingerprint_str); len = 0; p = strtok(tmp, ":"); if (p == NULL) { log0(0, 0, "Invalid fingerprint %s", fingerprint_str); return 0; } do { if (len >= HMAC_LEN) { log0(0, 0, "Key fingerprint %s too long", fingerprint_str); free(tmp); return 0; } errno = 0; num = strtol(p, NULL, 16); if (errno) { syserror(0, 0, "Parse of host key fingerprint %s failed", fingerprint_str); free(tmp); return 0; } else if ((num > 255) || (num < 0)) { log0(0, 0, "Parse of host key fingerprint %s failed", fingerprint_str); free(tmp); return 0; } fingerprint[len++] = (uint8_t)num; p = strtok(NULL, ":"); } while (p); free(tmp); return 1; } /** * Tests a sockaddr_u union to see if it's a valid multicast address */ int is_multicast(const union sockaddr_u *addr, int ssm) { int val; if (addr->ss.ss_family == AF_INET6) { if (addr->sin6.sin6_addr.s6_addr[0] == 0xff) { if (ssm && ((addr->sin6.sin6_addr.s6_addr[1] & 0x30) == 0)) { return 0; } else { return 1; } } else { return 0; } } else if (addr->ss.ss_family == AF_INET) { val = ntohl(addr->sin.sin_addr.s_addr) >> 24; if (ssm && (val != 232)) { return 0; } else if ((val >= 224) && (val < 240)) { return 1; } else { return 0; } } else { return 0; } } /** * Compares two sockaddr_u unions for equality * Returns 1 if address family, address, and port are equal, 0 otherwise */ int addr_equal(const union sockaddr_u *addr1, const union sockaddr_u *addr2) { if (addr1->ss.ss_family != addr2->ss.ss_family) { return 0; } if (addr1->ss.ss_family == AF_INET6) { if ((!memcmp(&addr1->sin6.sin6_addr, &addr2->sin6.sin6_addr, sizeof(struct in6_addr))) && (addr1->sin6.sin6_port == addr2->sin6.sin6_port)) { return 1; } else { return 0; } } else { if ((addr1->sin.sin_addr.s_addr == addr2->sin.sin_addr.s_addr) && (addr1->sin.sin_port == addr2->sin.sin_port)) { return 1; } else { return 0; } } } /** * Checks to see if a sockaddr_u union has a zero address * Returns 1 if the address is zero (for the given family), 0 otherwise. */ int addr_blank(const union sockaddr_u *addr) { if (addr->ss.ss_family == AF_INET6) { return (memcmp(&addr->sin6.sin6_addr, &in6addr_any, sizeof(struct in6_addr)) == 0); } else if (addr->ss.ss_family == AF_INET) { return (addr->sin.sin_addr.s_addr == INADDR_ANY); } else { return 1; } } /** * Converts a 64-bit value from host to network byte order */ uint64_t uftp_htonll(uint64_t val) { uint64_t rval; int i; unsigned char *p; p = (unsigned char *)&rval; for (i = 0; i < 8; i++) { p[7 - i] = (val & (0xFFLL << (i * 8))) >> (i * 8); } return rval; } /** * Converts a 64-bit value from network to host byte order */ uint64_t uftp_ntohll(uint64_t val) { uint64_t rval; int i; unsigned char *p; p = (unsigned char *)&val; for (i = 0, rval = 0; i < 8; i++) { rval |= (uint64_t)p[i] << ((7 - i) * 8); } return rval; } /** * Returns the effective length of a sockaddr type struct * based on the address family */ int family_len(union sockaddr_u addr) { if (addr.ss.ss_family == AF_INET6) { return sizeof(struct sockaddr_in6); } else if (addr.ss.ss_family == AF_INET) { return sizeof(struct sockaddr_in); } else { return sizeof(struct sockaddr_storage); } } /** * Returns whether the last socket operation would have blocked */ int would_block_err() { #ifdef WINDOWS return (WSAGetLastError() == WSAEWOULDBLOCK); #else return (errno == EAGAIN); #endif } /** * Returns whether a connection reset error occured */ int conn_reset_err(void) { #ifdef WINDOWS return (WSAGetLastError() == WSAECONNRESET); #else return (errno == ECONNRESET); #endif } /** * Calls sendto, retrying if the send would block. * The calling function should check for and log any other errors. */ int nb_sendto(SOCKET s, const void *msg, int len, int flags, const struct sockaddr *to, int tolen) { int retry, sentlen; retry = 1; while (retry) { if ((sentlen = sendto(s, msg, len, flags, to, tolen)) == SOCKET_ERROR) { if (!would_block_err()) { return -1; } } else { retry = 0; } } return sentlen; } /** * Reads a packet off the network with a possible timeout. * The socket must be non-blocking. * Returns 1 on success, 0 on timeout, -1 on fail. */ int read_packet(SOCKET sock, union sockaddr_u *su, unsigned char *buffer, int *len, int bsize, const struct timeval *timeout) { fd_set fdin; struct timeval tv; int rval; socklen_t addr_len; #ifdef BLOCKING FD_ZERO(&fdin); FD_SET(sock,&fdin); if (timeout) tv = *timeout; if ((rval = select(FD_SETSIZE-1, &fdin, NULL, NULL, (timeout ? &tv : NULL))) == SOCKET_ERROR) { sockerror(0, 0, "Select failed"); return -1; } if (rval == 0) { return 0; } else if (FD_ISSET(sock, &fdin)) { addr_len = sizeof(union sockaddr_u); if ((*len = recvfrom(sock, buffer, bsize, 0, (struct sockaddr *)su, &addr_len)) == SOCKET_ERROR) { if (!conn_reset_err()) { sockerror(0, 0, "Error receiving"); } return -1; } return 1; } else { log0(0, 0, "Unknown select error"); return -1; } #else // BLOCKING while (1) { addr_len = sizeof(union sockaddr_u); if ((*len = recvfrom(sock, buffer, bsize, 0, (struct sockaddr *)su, &addr_len)) == SOCKET_ERROR) { if (!would_block_err()) { if (!conn_reset_err()) { sockerror(0, 0, "Error receiving"); } return -1; } } else { return 1; } FD_ZERO(&fdin); FD_SET(sock,&fdin); if (timeout) tv = *timeout; if ((rval = select(FD_SETSIZE-1, &fdin, NULL, NULL, (timeout ? &tv : NULL))) == SOCKET_ERROR) { sockerror(0, 0, "Select failed"); return -1; } if (rval == 0) { return 0; } else if (!FD_ISSET(sock, &fdin)) { log0(0, 0, "Unknown select error"); return -1; } } #endif // BLOCKING } /** * Performs an XOR between p1 and p2, storing the result in p1 */ void memxor(void *p1, const void *p2, int len) { int i; for (i = 0; i < len; i++) { ((unsigned char *)p1)[i] ^= ((const unsigned char *)p2)[i]; } } /** * Constructs an initialization vector (IV) as follows: * For a 128-bit IV (AES non-auth): IV = S + src_ID + ctr * For a 96-bit IV (AES auth): IV = (S XOR src_ID) + ctr * For a 64-bit IV (DES, 3DES): IV = (S + src_ID) XOR ctr * All values other should be in network byte order. */ void build_iv(uint8_t *iv, const uint8_t *salt, int ivlen, uint64_t ivctr, uint32_t src_id) { char tmp[16], tmp2[16]; int tmplen, tmp2len; memset(tmp, 0, sizeof(tmp)); tmplen = 0; if (ivlen == 8) { memcpy(tmp, salt, SALT_LEN); tmplen = SALT_LEN; memcpy(tmp + tmplen, &src_id, sizeof(uint32_t)); tmplen += sizeof(uint32_t); memcpy(tmp2, &ivctr, sizeof(uint64_t)); tmp2len = sizeof(uint64_t); memxor(tmp, tmp2, tmp2len); } else if (ivlen == 12) { memcpy(tmp, salt, SALT_LEN); tmplen = SALT_LEN; memcpy(tmp2, &src_id, sizeof(uint32_t)); tmp2len = sizeof(uint32_t); memxor(tmp, tmp2, tmp2len); memcpy(tmp + tmplen, &ivctr, sizeof(uint64_t)); tmplen += sizeof(uint64_t); } else if (ivlen == 16) { memcpy(tmp, salt, SALT_LEN); tmplen = SALT_LEN; memcpy(tmp + tmplen, &src_id, sizeof(uint32_t)); tmplen += sizeof(uint32_t); memcpy(tmp + tmplen, &ivctr, sizeof(uint64_t)); tmplen += sizeof(uint64_t); } memcpy(iv, tmp, tmplen); } /** * Outputs data buffers to log in hex. * Used only for debugging */ void printhex(const char *name, const unsigned char *data, int len) { int i; sclog2("%s:", name); for (i = 0; i < len; i++) { sclog2(" %02X", data[i]); if (i % 16 == 15) sclog2("\n"); } sclog2("\n"); } /** * Returns 1 if the specified keytype is an authentication mode cipher */ int is_auth_enc(int keytype) { return ((keytype == KEY_AES128_GCM) || (keytype == KEY_AES256_GCM) || (keytype == KEY_AES128_CCM) || (keytype == KEY_AES256_CCM)); } /** * Returns 1 if the specified keytype is a GCM mode cipher */ int is_gcm_mode(int keytype) { return ((keytype == KEY_AES128_GCM) || (keytype == KEY_AES256_GCM)); } /** * Returns 1 if the specified keytype is a CCM mode cipher */ int is_ccm_mode(int keytype) { return ((keytype == KEY_AES128_CCM) || (keytype == KEY_AES256_CCM)); } /** * If the specified keytype is for an authentication cipher, * return the keytype for the same cipher in CBC mode. */ int unauth_key(int keytype) { switch (keytype) { case KEY_AES128_GCM: case KEY_AES128_CCM: return KEY_AES128_CBC; case KEY_AES256_GCM: case KEY_AES256_CCM: return KEY_AES256_CBC; default: return keytype; } } /** * Verify the signature of an encrypted message and decrypt. * The decrypted message is returned without a uftp_h header. * Returns 1 on success, 0 on fail */ int validate_and_decrypt(unsigned char *encpacket, unsigned int enclen, unsigned char **decpacket, unsigned int *declen, int keytype, const uint8_t *key, const uint8_t *salt, int ivlen, int hashtype, const uint8_t *hmackey, uint8_t hmaclen, int sigtype, int keyextype, union key_t pubkey, int pubkeylen) { struct uftp_h *header; struct encrypted_h *encrypted; unsigned char *payload, *sig, *sigcopy, *sigtest, *iv; unsigned int hsiglen, siglen, len, rval, allocdec; uint64_t ivctr; if (sigtype == SIG_HMAC) { siglen = hmaclen; } else if (sigtype == SIG_KEYEX) { siglen = pubkeylen; } else if (sigtype == SIG_AUTHENC) { siglen = 0; } else { log0(0, 0, "Invalid signature type"); return 0; } header = (struct uftp_h *)encpacket; encrypted = (struct encrypted_h *)(encpacket + sizeof(struct uftp_h)); hsiglen = ntohs(encrypted->sig_len); sig = (unsigned char *)encrypted + sizeof(struct encrypted_h); payload = sig + hsiglen; if (header->func != ENCRYPTED) { log0(0, 0, "Attempt to decrypt non-encrypted message"); return 0; } if (enclen != (sizeof(struct uftp_h) + sizeof(struct encrypted_h) + hsiglen + ntohs(encrypted->payload_len))) { log0(0, 0, "Invalid signature and/or encrypted payload length"); return 0; } if (hsiglen != siglen) { log0(0, 0, "Signature length incorrect: got %d, expected %d", hsiglen, siglen); return 0; } sigcopy = safe_calloc(siglen, 1); sigtest = safe_calloc(siglen, 1); iv = safe_calloc(ivlen, 1); if (sigtype != SIG_AUTHENC) { memcpy(sigcopy, sig, hsiglen); memset(sig, 0, hsiglen); } if (sigtype == SIG_HMAC) { if (!create_hmac(hashtype, hmackey, hsiglen, encpacket, enclen, sigtest, &len)) { log0(0, 0, "HMAC creation failed"); rval = 0; goto end; } if (memcmp(sigtest, sigcopy, len)) { log0(0, 0, "HMAC verification failed"); rval = 0; goto end; } } else if ((sigtype == SIG_KEYEX) && (keyextype == KEYEX_ECDH_ECDSA)) { if (!verify_ECDSA_sig(pubkey.ec, hashtype, encpacket, enclen, sigcopy, ntohs(encrypted->sig_len))) { log0(0, 0, "ECDSA signature verification failed"); rval = 0; goto end; } } else if ((sigtype == SIG_KEYEX) && ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA))) { if (!verify_RSA_sig(pubkey.rsa, hashtype, encpacket, enclen, sigcopy, ntohs(encrypted->sig_len))) { log0(0, 0, "RSA signature verification failed"); rval = 0; goto end; } } allocdec = 0; if (*decpacket == NULL) { allocdec = 1; *decpacket = safe_calloc(MAXMTU + KEYBLSIZE, 1); } ivctr = ntohl(encrypted->iv_ctr_lo); ivctr |= (uint64_t)ntohl(encrypted->iv_ctr_hi) << 32; build_iv(iv, salt, ivlen, uftp_htonll(ivctr), header->src_id); if (!decrypt_block(keytype, iv, key, encpacket, sizeof(struct uftp_h) + sizeof(struct encrypted_h), payload, ntohs(encrypted->payload_len), *decpacket, declen)) { log0(0, 0, "Decrypt failed"); if (allocdec) { free(*decpacket); *decpacket = NULL; } rval = 0; goto end; } rval = 1; end: free(sigcopy); free(sigtest); free(iv); return rval; } /** * Encrypts a message and attaches a signature to the encrypted message. * The incoming message should include a uftp_h header. * Returns 1 on success, 0 on fail */ int encrypt_and_sign(const unsigned char *decpacket, unsigned char **encpacket, int declen, int *enclen, int keytype, uint8_t *key, const uint8_t *salt, uint64_t *ivctr, int ivlen, int hashtype, uint8_t *hmackey, uint8_t hmaclen, int sigtype, int keyextype, union key_t privkey, int privkeylen) { struct uftp_h *header; struct encrypted_h *encrypted; const unsigned char *mheader; unsigned char *payload, *sig, *sigcopy, *iv; unsigned int payloadlen, siglen, len, allocenc; if (sigtype == SIG_HMAC) { siglen = hmaclen; } else if (sigtype == SIG_KEYEX) { siglen = privkeylen; } else if (sigtype == SIG_AUTHENC) { siglen = 0; } else { log0(0, 0, "Invalid signature type"); return 0; } allocenc = 0; if (*encpacket == NULL) { allocenc = 1; *encpacket = safe_calloc(MAXMTU + KEYBLSIZE, 1); } iv = safe_calloc(ivlen, 1); mheader = decpacket + sizeof(struct uftp_h); header = (struct uftp_h *)*encpacket; encrypted = (struct encrypted_h *)(*encpacket + sizeof(struct uftp_h)); sig = (unsigned char *)encrypted + sizeof(struct encrypted_h); payload = sig + siglen; (*ivctr)++; memcpy(*encpacket, decpacket, sizeof(struct uftp_h)); header->func = ENCRYPTED; encrypted->iv_ctr_hi = htonl((*ivctr & 0xFFFFFFFF00000000LL) >> 32); encrypted->iv_ctr_lo = htonl(*ivctr & 0x00000000FFFFFFFFLL); encrypted->sig_len = htons(siglen); if (sigtype == SIG_AUTHENC) { if (is_gcm_mode(keytype)) { encrypted->payload_len = htons(declen + GCM_TAG_LEN); } else if (is_ccm_mode(keytype)) { encrypted->payload_len = htons(declen + CCM_TAG_LEN); } } build_iv(iv, salt, ivlen, uftp_htonll(*ivctr), header->src_id); if (!encrypt_block(keytype, iv, key, *encpacket, sizeof(struct uftp_h) + sizeof(struct encrypted_h), mheader, declen, payload, &payloadlen)) { // Called function should log free(iv); if (allocenc) { free(*encpacket); *encpacket = NULL; } return 0; } free(iv); if (sigtype != SIG_AUTHENC) { encrypted->payload_len = htons(payloadlen); } else { if (payloadlen != ntohs(encrypted->payload_len)) { log0(0, 0, "Invalid payloadlen: got %d, expected %d", payloadlen, ntohs(encrypted->payload_len)); return 0; } } *enclen = sizeof(struct encrypted_h) + payloadlen + siglen; sigcopy = safe_calloc(siglen, 1); if (sigtype != SIG_AUTHENC) { memset(sig, 0, siglen); } if (sigtype == SIG_HMAC) { if (!create_hmac(hashtype, hmackey, siglen, *encpacket, sizeof(struct uftp_h) + *enclen, sigcopy, &len)) { log0(0, 0, "HMAC creation failed"); free(sigcopy); if (allocenc) { free(*encpacket); *encpacket = NULL; } return 0; } } else if ((sigtype == SIG_KEYEX) && (keyextype == KEYEX_ECDH_ECDSA)) { if (!create_ECDSA_sig(privkey.ec, hashtype, *encpacket, sizeof(struct uftp_h) + *enclen, sigcopy, &len)) { // Called function should log free(sigcopy); if (allocenc) { free(*encpacket); *encpacket = NULL; } return 0; } } else if ((sigtype == SIG_KEYEX) && ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA))) { if (!create_RSA_sig(privkey.rsa, hashtype, *encpacket, sizeof(struct uftp_h) + *enclen, sigcopy, &len)) { // Called function should log free(sigcopy); if (allocenc) { free(*encpacket); *encpacket = NULL; } return 0; } } if (sigtype != SIG_AUTHENC) { memcpy(sig, sigcopy, len); } free(sigcopy); return 1; } /** * Psedo-random function for an individual hashing algorithm * as defined in RFC 4346 and RFC 5246 */ static void P_hash(int hashtype, int bytes, const unsigned char *secret, int secret_len, const char *label, const unsigned char *seed, int seed_len, unsigned char *outbuf, int *outbuf_len) { unsigned char *newseed, *inbuf, *tmpbuf; unsigned newseed_len, inbuf_len; unsigned int tmpbuf_len, outbuf_len_new; newseed = safe_calloc(strlen(label) + seed_len, 1); inbuf = safe_calloc(get_hash_len(hashtype) + strlen(label) + seed_len, 1); tmpbuf = safe_calloc(get_hash_len(hashtype) + strlen(label) + seed_len, 1); *outbuf_len = 0; newseed_len = 0; memcpy(newseed, label, strlen(label)); newseed_len += strlen(label); memcpy(newseed + newseed_len, seed, seed_len); newseed_len += seed_len; memcpy(inbuf, newseed, newseed_len); inbuf_len = newseed_len; while (*outbuf_len < bytes) { // TODO: create_hmac can fail under CrypoAPI. // Figure out how to handle this. create_hmac(hashtype, secret, secret_len, inbuf, inbuf_len, tmpbuf, &tmpbuf_len); memcpy(tmpbuf + tmpbuf_len, newseed, newseed_len); tmpbuf_len += newseed_len; create_hmac(hashtype, secret, secret_len, tmpbuf, tmpbuf_len, outbuf + *outbuf_len, &outbuf_len_new); *outbuf_len += outbuf_len_new; memcpy(inbuf,tmpbuf,tmpbuf_len); inbuf_len = tmpbuf_len; } free(newseed); free(inbuf); free(tmpbuf); } /** * Psedo-random function * as defined in RFC 4346 and RFC 5246 */ void PRF(int hashtype, int bytes, const unsigned char *secret, int secret_len, const char *label, const unsigned char *seed, int seed_len, unsigned char *outbuf, int *outbuf_len) { int i, s_len, sha_buf_len, md5_buf_len; unsigned char *sha_buf, *md5_buf; if (hashtype == HASH_SHA256) { P_hash(HASH_SHA256, bytes, secret, secret_len, label, seed, seed_len, outbuf, outbuf_len); } else if (hashtype == HASH_SHA1) { // TLS 1.1 MD5/SHA1 combination md5_buf = safe_calloc(bytes + get_hash_len(HASH_MD5), 1); sha_buf = safe_calloc(bytes + get_hash_len(HASH_SHA1), 1); // if secret_len is even integer division truncates the result // if secret_len is odd, the + 1 effectively rounds up s_len = (secret_len + 1) / 2; P_hash(HASH_MD5, bytes, secret, s_len, label, seed, seed_len, md5_buf, &md5_buf_len); P_hash(HASH_SHA1, bytes, secret + (secret_len - s_len), s_len, label, seed, seed_len, sha_buf, &sha_buf_len); for (i = 0; i < bytes; i++) { outbuf[i] = md5_buf[i] ^ sha_buf[i]; } *outbuf_len = bytes; free(md5_buf); free(sha_buf); } else { *outbuf_len = 0; } } /** * Outputs a key's fingerprint */ const char *print_key_fingerprint(const union key_t key, int keytype) { static char fpstr[100]; char *p; unsigned char *keyblob, fingerprint[HMAC_LEN]; uint16_t bloblen; unsigned int fplen, i, cnt; keyblob = safe_calloc(PUBKEY_LEN, 1); if (keytype == KEYBLOB_RSA) { if (!export_RSA_key(key.rsa, keyblob, &bloblen)) { free(keyblob); return NULL; } } else { if (!export_EC_key(key.ec, keyblob, &bloblen)) { free(keyblob); return NULL; } } hash(HASH_SHA1, keyblob, bloblen, fingerprint, &fplen); for (i = 0, p = fpstr; i < fplen; i++) { if (i != 0) { *p = ':'; p++; } cnt = snprintf(p, 3, "%02X", fingerprint[i]); p += cnt; } free(keyblob); return fpstr; } #if ((!defined WINDOWS) && (defined MCAST_JOIN_GROUP) &&\ (!defined NO_MCAST_JOIN)) /** * Join the specified multicast group on the specified list of interfaces. * If source specific multicast is supported and we're given a list of servers, * join source specific multicast groups for those servers. * Returns 1 on success, 0 on fail */ int multicast_join(SOCKET s, uint32_t group_id, const union sockaddr_u *multi, const struct iflist *addrlist, int addrlen, const struct fp_list_t *fplist, int fplist_len) { struct group_req greq; struct group_source_req gsreq; int level, i, j; for (i = 0; i < addrlen; i++) { if (!addrlist[i].ismulti) { continue; } if (addrlist[i].su.ss.ss_family != multi->ss.ss_family) { continue; } if (addrlist[i].su.ss.ss_family == AF_INET6) { level = IPPROTO_IPV6; } else if (addrlist[i].su.ss.ss_family == AF_INET) { level = IPPROTO_IP; } if (fplist_len == 0) { greq.gr_interface = addrlist[i].ifidx; greq.gr_group = multi->ss; if (setsockopt(s, level, MCAST_JOIN_GROUP, (char *)&greq, sizeof(greq)) == -1) { sockerror(group_id, 0, "Error joining multicast group"); return 0; } } else { for (j = 0; j < fplist_len; j++) { if (addrlist[i].su.ss.ss_family!=fplist[j].addr.ss.ss_family) { continue; } gsreq.gsr_interface = addrlist[i].ifidx; gsreq.gsr_source = fplist[j].addr.ss; gsreq.gsr_group = multi->ss; if (setsockopt(s, level, MCAST_JOIN_SOURCE_GROUP, (char *)&gsreq, sizeof(gsreq)) == -1) { sockerror(group_id, 0, "Error joining multicast group"); return 0; } } } } return 1; } /** * Leave the specified multicast group on the specified list of interfaces. * If source specific multicast is supported and we're given a list of servers, * leave source specific multicast groups for those servers. */ void multicast_leave(SOCKET s, uint32_t group_id, const union sockaddr_u *multi, const struct iflist *addrlist, int addrlen, const struct fp_list_t *fplist, int fplist_len) { struct group_req greq; struct group_source_req gsreq; int level, i, j; for (i = 0; i < addrlen; i++) { if (!addrlist[i].ismulti) { continue; } if (addrlist[i].su.ss.ss_family != multi->ss.ss_family) { continue; } if (addrlist[i].su.ss.ss_family == AF_INET6) { level = IPPROTO_IPV6; } else if (addrlist[i].su.ss.ss_family == AF_INET) { level = IPPROTO_IP; } if (fplist_len == 0) { greq.gr_interface = addrlist[i].ifidx; greq.gr_group = multi->ss; if (setsockopt(s, level, MCAST_LEAVE_GROUP, (char *)&greq, sizeof(greq)) == -1) { sockerror(group_id, 0, "Error leaving multicast group"); } } else { for (j = 0; j < fplist_len; j++) { if (addrlist[i].su.ss.ss_family!=fplist[j].addr.ss.ss_family) { continue; } gsreq.gsr_interface = addrlist[i].ifidx; gsreq.gsr_source = fplist[j].addr.ss; gsreq.gsr_group = multi->ss; if (setsockopt(s, level, MCAST_LEAVE_SOURCE_GROUP, (char *)&gsreq, sizeof(gsreq)) == -1) { sockerror(group_id, 0, "Error leaving multicast group"); } } } } } #else /** * Join the specified multicast group on the specified list of interfaces. * If source specific multicast is supported and we're given a list of servers, * join source specific multicast groups for those servers. * Returns 1 on success, 0 on fail */ int multicast_join(SOCKET s, uint32_t group_id, const union sockaddr_u *multi, const struct iflist *addrlist, int addrlen, const struct fp_list_t *fplist, int fplist_len) { struct ip_mreq mreq; struct ipv6_mreq mreq6; int i; for (i = 0; i < addrlen; i++) { if (!addrlist[i].ismulti) { continue; } if (addrlist[i].su.ss.ss_family != multi->ss.ss_family) { continue; } if (multi->ss.ss_family == AF_INET6) { mreq6.ipv6mr_multiaddr = multi->sin6.sin6_addr; mreq6.ipv6mr_interface = addrlist[i].ifidx; if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq6, sizeof(mreq6)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error joining multicast group"); return 0; } } else { #ifdef IP_ADD_SOURCE_MEMBERSHIP if (fplist_len != 0) { int j; for (j = 0; j < fplist_len; j++) { struct ip_mreq_source srcmreq; srcmreq.imr_multiaddr = multi->sin.sin_addr; srcmreq.imr_sourceaddr = fplist[j].addr.sin.sin_addr; srcmreq.imr_interface = addrlist[i].su.sin.sin_addr; if (setsockopt(s, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&srcmreq, sizeof(srcmreq)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error joining multicast group"); return 0; } } } else { mreq.imr_multiaddr = multi->sin.sin_addr; mreq.imr_interface = addrlist[i].su.sin.sin_addr; if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error joining multicast group"); return 0; } } #else mreq.imr_multiaddr = multi->sin.sin_addr; mreq.imr_interface = addrlist[i].su.sin.sin_addr; if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error joining multicast group"); return 0; } #endif } } return 1; } /** * Leave the specified multicast group on the specified list of interfaces. * If source specific multicast is supported and we're given a list of servers, * leave source specific multicast groups for those servers. */ void multicast_leave(SOCKET s, uint32_t group_id, const union sockaddr_u *multi, const struct iflist *addrlist, int addrlen, const struct fp_list_t *fplist, int fplist_len) { struct ip_mreq mreq; struct ipv6_mreq mreq6; int i; for (i = 0; i < addrlen; i++) { if (!addrlist[i].ismulti) { continue; } if (addrlist[i].su.ss.ss_family != multi->ss.ss_family) { continue; } if (multi->ss.ss_family == AF_INET6) { mreq6.ipv6mr_multiaddr = multi->sin6.sin6_addr; mreq6.ipv6mr_interface = addrlist[i].ifidx; if (setsockopt(s, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *)&mreq6, sizeof(mreq6)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error leaving multicast group"); } } else { #ifdef IP_DROP_SOURCE_MEMBERSHIP if (fplist_len != 0) { int j; for (j = 0; j < fplist_len; j++) { struct ip_mreq_source srcmreq; srcmreq.imr_multiaddr = multi->sin.sin_addr; srcmreq.imr_sourceaddr = fplist[j].addr.sin.sin_addr; srcmreq.imr_interface = addrlist[i].su.sin.sin_addr; if (setsockopt(s, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, (char *)&srcmreq, sizeof(srcmreq)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error leaving multicast group"); } } } else { mreq.imr_multiaddr = multi->sin.sin_addr; mreq.imr_interface = addrlist[i].su.sin.sin_addr; if (setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error leaving multicast group"); } } #else mreq.imr_multiaddr = multi->sin.sin_addr; mreq.imr_interface = addrlist[i].su.sin.sin_addr; if (setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) == SOCKET_ERROR) { sockerror(group_id, 0, "Error leaving multicast group"); } #endif } } } #endif // MCAST_JOIN_GROUP /** * Search for a network interface in a list with the matching name or index. * The name is formatted as interface/ip_version, ex. eth0/6, 2/4. * If ip_version is not given, defaults to IPv4. * Returns the index in the list if found, -1 if not found. */ int getifbyname(const char *name, const struct iflist *list, int len) { char *tmpname, *p, *ptr; int family, idx, i; tmpname = strdup(name); if (tmpname == NULL) { syserror(0, 0, "strdup failed!"); exit(1); } p = strchr(tmpname, '/'); if (p == NULL) { family = AF_INET; } else { p[0] = 0; if (p[1] == '6') { family = AF_INET6; } else if (p[1] == '4') { family = AF_INET; } else { free(tmpname); return -1; } } errno = 0; idx = strtoul(tmpname, &ptr, 10); if ((errno == 0) && (*ptr == '\x0')) { for (i = 0; i < len; i++) { if ((idx == list[i].ifidx) && (list[i].su.ss.ss_family == family)) { free(tmpname); return i; } } } else { for (i = 0; i < len; i++) { if ((!strcmp(tmpname, list[i].name)) && (list[i].su.ss.ss_family == family)) { free(tmpname); return i; } } } free(tmpname); return -1; } /** * Search for a network interface in a list with the matching IP address. * Returns the index in the list if found, -1 if not found. */ int getifbyaddr(union sockaddr_u *su, const struct iflist *list, int len) { int i; for (i = 0; i < len; i++) { if (su->ss.ss_family == list[i].su.ss.ss_family) { if (su->ss.ss_family == AF_INET) { if (su->sin.sin_addr.s_addr == list[i].su.sin.sin_addr.s_addr) { return i; } } else if (su->ss.ss_family == AF_INET6) { if (!memcmp(&su->sin6.sin6_addr, &list[i].su.sin6.sin6_addr, sizeof(struct in6_addr))) { return i; } } } } return -1; } /** * Reads buflen bytes into buf from the given file descriptor. * If buflen bytes are read, returns buflen. * If 0 bytes are read, returns 0 if allow_eof is true, otherwise returns -1. * If less that buflen bytes are read, or on error, returns -1. */ int file_read(int fd, void *buf, int buflen, int allow_eof) { int read_len; if ((read_len = read(fd, buf, buflen)) == -1) { syserror(0, 0, "Read failed"); return -1; } if ((read_len != buflen) && (!allow_eof || (read_len != 0))) { log0(0, 0, "Read error: read %d bytes, expected %d", read_len, buflen); return -1; } return read_len; } /** * Writes buflen bytes from buf to the given file descriptor. * If buflen bytes are written, returns buflen. * If less that buflen bytes are written, or on error, returns -1. */ int file_write(int fd, const void *buf, int buflen) { int write_len; if ((write_len = write(fd, buf, buflen)) == -1) { syserror(0, 0, "Write failed"); return -1; } if (write_len != buflen) { log0(0, 0, "Write error: wrote %d bytes, expected %d",write_len,buflen); return -1; } return write_len; } /** * Returns the free disk space in bytes of the filesystem that contains * the given file. Returns 2^63-1 on error. */ int64_t free_space(const char *file) { #ifdef WINDOWS ULARGE_INTEGER bytes_free; char *dirname, *filename; split_path(file, &dirname, &filename); if (dirname == NULL) { free(dirname); free(filename); return 0x7FFFFFFFFFFFFFFFULL; } if (!GetDiskFreeSpaceEx(dirname, &bytes_free, NULL, NULL)) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); log0(0, 0, "Error in GetDiskFreeSpaceEx: %s", errbuf); free(dirname); free(filename); return 0x7FFFFFFFFFFFFFFFULL; } else { log3(0, 0, "Free space: %s", printll(bytes_free.QuadPart)); free(dirname); free(filename); return bytes_free.QuadPart; } #else struct statvfs buf; if (statvfs(file, &buf) == -1) { syserror(0, 0, "statvfs failed"); return 0x7FFFFFFFFFFFFFFFULL; } else { log3(0, 0, "Free space: %s", printll((uint64_t)buf.f_bsize * buf.f_bavail)); return (int64_t)buf.f_bsize * buf.f_bavail; } #endif } /** * Determines if the priority value passed in is valid. * Returns 1 on success, 0 on fail */ int valid_priority(int priority) { #ifdef WINDOWS if ((priority >= -2) && (priority <= 2)) { return 1; } else { return 0; } #else if ((priority >= -20) && (priority <= 19)) { return 1; } else { return 0; } #endif } /** * Returns a 32-bit random number. * Some implementations of rand() generate values from 0 to 32767, * so this guarantees we get a full 32 bits. */ uint32_t rand32() { return((rand() & 0x7FFF) << 17) | ((rand() & 0x7FFF) << 2) | (rand() & 0x2); } /** * Safe malloc routine that always returns non-NULL * On error, exit() */ void *safe_malloc(size_t size) { void *p = malloc(size); if (p == NULL) { syserror(0, 0, "malloc failed!"); exit(1); } return p; } /** * Safe calloc routine that always returns non-NULL * On error, exit() */ void *safe_calloc(size_t num, size_t size) { void *p = calloc(num, size); if (p == NULL) { syserror(0, 0, "calloc failed!"); exit(1); } return p; } #define RTT_MIN 1.0e-6 #define RTT_MAX 1000.0 /** * Convert grtt from a double to a single byte. * As defined in RFC 5401 */ uint8_t quantize_grtt(double rtt) { if (rtt > RTT_MAX) { rtt = RTT_MAX; } else if (rtt < RTT_MIN) { rtt = RTT_MIN; } if (rtt < (33.0 * RTT_MIN)) { return ((uint8_t)(rtt / RTT_MIN) - 1); } else { return ((uint8_t)(0 + ceil(255.0 - (13.0 * log(RTT_MAX/rtt))))); } } /** * Convert grtt from a single byte to a double * As defined in RFC 5401 */ double unquantize_grtt(uint8_t rtt) { return ((rtt <= 31) ? (((double)(rtt + 1)) * (double)RTT_MIN) : (RTT_MAX / exp(((double)(255 - rtt)) / (double)13.0))); } /** * Convert the group size from an int to an 8-bit float (5 bit M, 3 bit E) */ uint8_t quantize_gsize(int size) { double M; int E; int rval; M = size; E = 0; while (M >= 10) { M /= 10; E++; } rval = ((int)((M * 32.0 / 10.0) + 0.5)) << 3; if (rval > 0xFF) { M /= 10; E++; rval = ((int)((M * 32.0 / 10.0) + 0.5)) << 3; } rval |= E; return rval; } /** * Convert the group size from an 8-bit float to an int (5 bit M, 3 bit E) */ int unquantize_gsize(uint8_t size) { int E, i; double rval; E = size & 0x7; rval = (size >> 3) * (10.0 / 32.0); for (i = 0; i < E; i++) { rval *= 10; } return (int)(rval + 0.5); } /** * Convert rate from an int to a 16-bit float * As defined in RFC 5740 */ uint16_t quantize_rate(uint32_t rate) { int E; double M; int rval; M = rate; E = 0; while (M > 10) { M /= 10; E++; } rval = (((int)(M * 4096.0 / 10.0 + 0.5)) << 4); if (rval > 0xFFFF) { M /= 10; E++; rval = (((int)(M * 4096.0 / 10.0 + 0.5)) << 4); } rval |= E; return rval; } /** * Convert rate in B/s from a 16-bit float to an int * As defined in RFC 5740 */ uint32_t unquantize_rate(uint16_t rate) { int E, i; double rval; E = rate & 0xF; rval = (rate >> 4) * (10.0 / 4096.0); for (i = 0; i < E; i++) { rval *= 10; } // For now, cap at max uint32 (~4.2GB/s) return (uint32_t)(rval > 0xFFFFFFFF ? 0xFFFFFFFF : rval); } uftp-4.1.5/proxy.h0000644000076400007640000002115612176066071013050 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #ifndef _PROXY_H #define _PROXY_H #include "uftp_common.h" #include "encryption.h" #define MAX_PEND 10 #define MAXLIST 100 #define KEY_REQ_LIMIT 5 /** * Type of proxy */ enum proxy_type { UNDEF_PROXY = 0, /// Not specified, indicates an error SERVER_PROXY = 1, /// Server proxy: forwards to a specific place CLIENT_PROXY = 2, /// Client proxy: sends to specified destaddr RESPONSE_PROXY = 3, /// Response proxy: response aggregation only }; /** * The state of the given group */ enum proxy_phase { PR_PHASE_REGISTERED = 1, /// Currently setting up group PR_PHASE_READY = 2, /// Still in setup, but received KEYINFO PR_PHASE_RECEIVING = 3, /// Group setup complete PR_PHASE_DONE = 4, /// All clients send COMPLETE for group }; /** * The state of a given client when encryption is enabled */ enum proxy_client_state { PR_CLIENT_MUTE = 0, /// Got nothing yet PR_CLIENT_REGISTERED = 1, /// Got REGISTER (and CLIENT_KEY if required) PR_CLIENT_CONF = 2, /// Got REG_CONF from server PR_CLIENT_READY = 3, /// Got INFO_ACK in response to KEYINFO PR_CLIENT_DONE = 4, /// Sent COMPLETE for group }; /** * Info for a particular client for the given group */ struct pr_destinfo_t { char name[DESTNAME_LEN]; /// Hostname of client uint32_t id; /// UID of client (network byte order) union key_t pubkey; /// The client's public key int pubkeylen; /// The length of client's key in bytes union key_t dhkey; /// The client's ECDH public key uint8_t verifydata[PUBKEY_LEN]; /// The verify data from a CLIENT_KEY uint16_t verifylen; /// The length of verifydata in bytes int registered; /// True if we received a REGISTER struct timeval regtime; /// Timestamp from last REGISTER int verified; /// True if we have a verified CLIENT_KEY int state; /// State as specified by proxy_client_state int pending; /// Index of pending message uint8_t rand2[RAND_LEN]; /// Client's random number uint8_t premaster[MASTER_LEN]; /// Premaster secret sent by client unsigned int premaster_len; /// Length of premaster secret uint8_t master[MASTER_LEN]; /// Master key for client uint8_t hmackey[HMAC_LEN]; /// HMAC key for client uint8_t key[MAXKEY]; /// Symmetric encryption key for client uint8_t salt[MAXIV]; /// Salt for block cypher IV for client }; /** * Info for a message pending to go upstream */ struct pr_pending_info_t { int msg, count; /// Type and number of pending responses uint16_t file_id; /// File ID from last client message struct timeval tstamp; /// Timestamp from last client message struct timeval rx_tstamp; /// Time last client message received uint16_t section; /// Section number from last status uint8_t *naklist; /// NAK list from last status uint8_t partial; /// PARTIAL flag from last FILEINFO_ACK uint8_t comp_status; /// status flag from a COMPLETE }; /** * Info for a particular group */ struct pr_group_list_t { uint32_t group_id; /// Group ID uint8_t group_inst; /// Group instance ID (restart number) uint8_t version; /// Protocol version number of server uint32_t src_id; /// ID of server double grtt; /// Server's GRTT uint8_t robust, cc_type; /// Robust factor, congestion control type uint32_t gsize; /// Group size estimate int send_seq_up; /// Outgoing upstream seq. number int send_seq_down; /// Outgoing downstream seq. number union sockaddr_u publicmcast, privatemcast; int multi_join; /// True if we're listening on private addr unsigned int blocksize; /// Size of packet payload unsigned int datapacketsize; /// Max size of UFTP packet union sockaddr_u up_addr; /// Upstream addr to send responses back to struct timeval phase_expire_time, phase_timeout_time, timeout_time; struct timeval start_phase_timeout_time, start_timeout_time; int phase, client_auth; int keyinfo_cnt; struct pr_pending_info_t pending[MAX_PEND]; /// Pending messages to send uint8_t last_seq; /// Last sequence number used in STATUS int keytype, hashtype, sigtype, keyextype; /// Encryption parameters union key_t server_pubkey; /// Server's RSA public key union key_t proxy_privkey; /// Proxy's RSA private key for this group union key_t server_dhkey; /// Server ECDH public key for this group union key_t proxy_dhkey; /// Proxy ECDH public key for this group unsigned int server_pubkeylen; /// Length in bytes of server key unsigned int proxy_privkeylen; /// Length in bytes of proxy key uint8_t rand1[RAND_LEN]; /// Server's random number uint8_t rand2[RAND_LEN]; /// Proxy's random number uint8_t premaster[MASTER_LEN]; /// Premaster secret sent by proxy unsigned int premaster_len; /// Length of premaster secret uint8_t master[MASTER_LEN]; /// Master key for proxy uint8_t hmackey[HMAC_LEN]; /// HMAC key for proxy uint8_t key[MAXKEY]; /// Symmetric encryption key for proxy uint8_t salt[MAXIV]; /// Salt for block cypher IV for proxy uint8_t groupmaster[MASTER_LEN];/// Master key for server uint8_t grouphmackey[HMAC_LEN]; /// HMAC key for server uint8_t groupkey[MAXKEY]; /// Symmetric encryption key for server uint8_t groupsalt[MAXIV]; /// Salt for block cypher IV for server uint64_t ivctr; /// Counter portion of the IV int ivlen, keylen, hmaclen; /// Length of HMAC key, symmetric key and iv struct pr_destinfo_t destinfo[MAXPROXYDEST]; /// List of clients int destcount; /// Number of clients served by this proxy }; /** * Global command line values and sockets */ extern SOCKET listener; extern char pidfile[MAXPATHNAME]; extern char keyfile[MAXLIST][MAXPATHNAME], keyinfo[MAXLIST][MAXPATHNAME]; extern int proxy_type, debug, rcvbuf, dscp, keyfile_count, keyinfo_count; extern int hb_interval, priority; extern unsigned int ttl; char portname[PORTNAME_LEN], out_portname[PORTNAME_LEN]; int port, out_port; extern union sockaddr_u down_addr; extern int have_down_fingerprint; extern uint8_t down_fingerprint[HMAC_LEN]; extern uint32_t down_nonce, uid; extern union sockaddr_u hb_hosts[MAXLIST]; extern union sockaddr_u pub_multi[MAX_INTERFACES]; extern struct fp_list_t server_fp[MAXLIST], client_fp[MAXPROXYDEST]; extern struct iflist ifl[MAX_INTERFACES], m_interface[MAX_INTERFACES]; extern struct timeval next_hb_time, last_key_req; extern int ifl_len, hbhost_count, server_fp_count, client_fp_count; extern int key_count, pub_multi_count, interface_count, sys_keys; extern struct iflist out_if; extern union key_t privkey[MAXLIST]; extern int privkey_type[MAXLIST]; extern union key_t dhkey; extern uint8_t ecdh_curve; extern struct pr_group_list_t group_list[MAXLIST]; #endif // _PROXY_H uftp-4.1.5/uftpproxyd.10000644000076400007640000006010312176066071014017 0ustar dbushdbush.TH uftpproxyd 1 "30 July 2013" "UFTP 4.1" .SH NAME uftpproxyd - Encrypted UDP based ftp with multicast - proxy daemon .SH SYNOPSIS uftpproxyd { -s { dest | fp=fingerprint } | -c | -r } [ -d ] [ -p port ] [ -t ttl ] [ -Q dscp ] [ -N priority ] [ -O out_multi_interface ] [ -U UID ] [ -q dest_port ] [ -m ] [ -x log_level ] [ -H hb_server[:port][,hb_server[:port]...] ] [ -g max_log_size ] [ -n max_log_count ] [ -h hb_interval ] [ -B udp_buf_size ] [ -L logfile ] [ -P pidfile ] [ -C clientlist_file ] [ -S serverlist_file ] [ -k keyfile[,keyfile...] ] [ -K rsa:key_len | ec:curve[,rsa:key_len | ec:curve...]] [ -e ecdh_curve ] [ -I interface[,interface...] ] [ -M pub_mcast_addr[,pub_mcast_addr...] ] .SH DESCRIPTION .P uftpproxyd is the proxy daemon of the UFTP suite. It performs multicast tunneling, NAT traversal, and client response aggregation. It is used in one of two scenarios. The first is when the server and one or more clients are on separate networks and cannot be reached directly via multicast, and/or one or both sides are behind a firewall or NAT\(aqed. This allows applications to function when there is little to no access to routers. The second is when the server can contact clients directly but there are too many of them to directly handle the responses. This allows greater scalability. The proxy can run in one of three modes: a server proxy, a client proxy, or response proxy. A server proxy is typically local to a server and acts as the upstream end of a multicast tunnel. It listens on the public multicast address (and private multicast address when specified) and forwards downstream packets to a specific address downstream. Upstream packets are forwarded back where the announcement originated from. A client proxy is typically local to one or more clients and forms the downstream end of a multicast tunnel. It receives unicast data from one or more server proxies and forwards downstream traffic to the multicast address specified in the packet header. Upstream traffic from clients is gathered and forwarded back where the announcement came from as an aggregated response. If a client proxy is behind a firewall, the proxy can send a heartbeat message to the upstream proxy to make a pinhole in the firewall that the upstream server proxy can connect to. If the client proxy is also NATed, the upstream server proxy may not know the IP/port of the client proxy, so the server proxy can be configured to wait for a heartbeat message, and use the IP/port the heartbeat came from as its downstream address. If the server proxy is also behind a firewall or NAT, a second server proxy on a machine with a publicly accessible IP can be inserted between the first server proxy and the client proxy. In this case, the first server proxy is set up to use the second as its downstream address, and the second server proxy is set up to use the first heartbeat it receives from a client proxy as its downstream address. A response proxy functions as a response aggregator in situations where the server has direct multicast accessibility to clients but the number of clients are to high for the server to handle itself. It listens on the public multicast address (and private multicast address when specified), but does not forward packets from the server since those packets reach clients directly. It does however send some messages directly to clients in the process of establishing encryption keys. Upstream traffic from clients is gathered and forwarded back where the announcement came from as an aggregated response. Clients in this environment are configured to send all responses to a specific response proxy. Messages sent directly from response proxies to clients use multicast (either the primary public address, or the private address, depending on the message). .SH EXAMPLES .SS Server / Client Proxies .nf Figure 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x Network A x x ---------- x x | Server | x x ---------- x x | x x | multicast x x | x x |----------------------------------------- x x | | | x x v v v x x ---------------- ---------------- ---------- x x | Server Proxy | | Server Proxy | | Client | x x ---------------- ---------------- ---------- x x | | x x | unicast | unicast x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | | | ------------ | | xxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx x | Network B x x | Network C x x v x x v x x ---------------- x x ---------------- x x | Client Proxy | x x | Client Proxy | x x ---------------- x x ---------------- x x | x x | x x | multicast x x | multicast x x | x x | x x |------------- x x |------------ x x | | x x | | x x v v x x v v x x ---------- ---------- x x ---------- ---------- x x | Client | | Client | x x | Client | | Client | x x ---------- ---------- x x ---------- ---------- x x x x x xxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx .fi .P In Figure 1 above there are a server and five clients. The server and one client are on network A, two clients are on network B, and two clients are on network C. There is one client proxy on network B and one on network C. On network A are two server proxies, one configured to send to the client proxy on network B and the other configured to send to the client proxy on network C. Client proxies normally should NOT run on the same machine as a client. Doing so can result in the server getting confused when it sees messages coming from a proxy and a client with the same IP and therefore cannot tell the difference. This can only work if the machine has multiple IPs and the client proxy and client listen on different IPs. NOTE: When using proxies in environments where private IP addresses are in use (10.x.x.x, 172.16-31.x.x, 192.168.x.x), it is strongly recommended to assign a unique ID to each client and client proxy, and for servers to call out clients by unique ID instead of name/IP. This prevents IP address collisions at the server between two clients with the same local IP. .SS Response Proxies .nf Figure 2 ---------- |-->| Server | | ---------- | | | | multicast | | | |-------------------------------------- | | | | | | | v | v | | ------------------ | ------------------ | | | Response Proxy | | | Response Proxy | | v ------------------ v ------------------ | ---------- ^ | ---------- ^ | | | Client | | | | Client | | | | ---------- | | ---------- | | | | | | | | | | | | | | | | | ----------- | ------------ | | client response | client response | | | | | proxy response | | ----------------------------------------------------- .fi .P Figure 2 shows a simplified setup involving a server, two clients, and two response proxies, all on the same network segment. In this environment, multicast messages from each proxy reaches both clients, not just the client it serves. .nf Figure 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x Network A x x ---------- x x ->| Server |<---------------------------------- x x | ---------- | x x | | | x x | | multicast | x x | | | x x | | | x x | ------------------------------------------ | x x | | | | | | x x | | v | v | x x | | ------------------ | ------------------ x x | | | Response Proxy | | | Response Proxy | x x | | ------------------ | ------------------ x x | | | ^ | ^ x x |/|\\---- | | | x x | | ----/|\\----------- x x | | | | x x | | | | x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxx | | | | | ------------|| | xxxxxxxxxxxxxxxxxxxxxxxxxxxx || xxxxxxxxxxxxxxxxxxxxxxxxxxxx x | Network B x || x | Network C x x | x || x | x x | x || x | x x ------------------ x || x ------------------ x x | | x || x | | x x v v x || x v v x x ---------- ---------- x || x ---------- ---------- x x | Client | | Client | x || x | Client | | Client | x x ---------- ---------- x || x ---------- ---------- x x | | x || x | | x x -------------------x-||-x-------------------- x x x x x xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx .fi .P In Figure 3, there are two response proxies local to the server and four clients in two remote networks, with each response proxy handling the clients from one network. Multicast messages from each proxy would reach all clients, not just the clients it serves. Even though the proxies are offloading work from the server in handling client responses, the server\(aqs network still has to handle responses from all clients since the proxies are on the server\(aqs network. As a result, this setup has limited scalability. .nf Figure 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x Network A x x ---------- x x ->| Server |<--------------x---------------- x | ---------- x | x | | x | x | | multicast x | x | | x | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | | | | | |-------------------------- | | | | | xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx x | | Network B1 x x | | Network C1 x x | ------- x x |------- | x x | | | x x | | | x x | | v x x | v | x x | | ------------------ x x | ------------------ x x | | | Response Proxy | x x | | Response Proxy | x x | | ------------------ x x | ------------------ x x | | | ^ x x | ^ x x |/|\\---- | x x | | x x | | x --x-/|\\----------- x x | | x | x | x x | | x | x | x xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | | | | | ------------|| | xxxxxxxxxxxxxxxxxxxxxxxxxxxx || xxxxxxxxxxxxxxxxxxxxxxxxxxxx x | Network B2 x || x | Network C2 x x | x || x | x x | x || x | x x ------------------ x || x ------------------ x x | | x || x | | x x v v x || x v v x x ---------- ---------- x || x ---------- ---------- x x | Client | | Client | x || x | Client | | Client | x x ---------- ---------- x || x ---------- ---------- x x | | x || x | | x x -------------------x-||-x-------------------- x x x x x xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx .fi .P In Figure 4, each proxy is at least one hop away from the clients it serves, and at least one hop away from the server. In this case, multicast messages from each proxy only go to the clients it serves. Also, since the proxies are not on the same network as the server, messages coming from the client don\(aqt have any effect on the server\(aqs local network. A setup like this is the most scalabile, and is the most flexible since another server on a different network can utilize the response proxies in the same way. .SH OPTIONS .P The following options are supported: .TP .B \-s { dest | fp=fingerprint } Sets up the proxy as a server proxy. If dest is specified, this is the name/IP of the downstream client proxy. If fingerprint is specified, this designates the public key signature of the downstream proxy. When this proxy gets a heartbeat message signed with the matching key, it will use the source IP:port of the heartbeat for its downstream address. Exactly one of -s, -c, or -r must be specified. .TP .B \-c Sets up the proxy as a client proxy. Exactly one of -s, -c, or -r must be specified. .TP .B \-r[:curve] Sets up the proxy as a response proxy. If "curve" is given, specifies the EC curve to use for ECDH key exchange (see -k and -K for details), otherwise no ECDH key is generated. Exactly one of -s, -c, or -r must be specified. .TP .B \-d Enable debug mode. The process will run in the foreground and all output will go to stderr. If specified, the -L option is ignored. .TP .B \-p port The UDP port number to listen on. Default is 1044. .TP .B \-t ttl Specifies the time-to-live for multicast packets. Default is 1. .TP .B \-N priority Sets the process priority. On Windows systems, valid values are from -2 to 2, with a default of 0. These correspond to the following priorities: .nf -2 High -1 Above Normal 0 Normal 1 Below Normal 2 Low .fi On all other systems, this is the "nice" value. Valid values are from -20 to 19, where -20 is the highest priority and 19 is the lowest priority. Default is 0. .TP .B \-O out_multi_interface The interface to send the data from. Can be specified either by interface name, by hostname, or by IP. If not specified, the default system interface is used. Applies only to client proxies. .TP .B \-U UID The unique ID for this proxy. May be specified either as a 6 digit hexadecimal number (0xnnnnnn) or as an IP address of the form 0.n.n.n. .TP .B \-q dest_port The port number of the downstream proxy (for server proxies) or clients (for client proxies). .TP .B \-m For Windows systems using CryptoAPI or CNG, private keys are normally stored in the key container of the running user. Specifying this option stores keys in the system key container. Useful when running as a service. On non-Windows systems, this option has no effect. .TP .B \-x log_level Specifies current logging level. Valid values are 0-5, with 0 being the least verbose and 5 being the most verbose. Default is 2, which is consistent with logging prior to version 3.5. .TP .B -H hb_server[:port][,hb_server[:port]...] Lists one or more proxies to send heartbeat messages to. When sending a signed heartbeat message, the first key listed under -k is used to sign the message. If port is not specified for a given proxy, the default port of 1044 is assumed. .TP .B -h hb_interval The time in seconds between sending heartbeat messages. Ignored if -H is not specified. .TP .B \-g max_log_size Specifies the maximum log file size in MB. Once the log file reaches this size, the file is renamed with a .1 extension and a new log file is opened. For example, if the log file is /tmp/uftpproxyd.log, it will be renamed /tmp/uftpproxyd.log.1 and a new /tmp/uftpproxyd.log will be created. Ignored if -d is specified. Valid values are 1-1024. Default is no log rolling. .TP .B \-n max_log_count Specifies the maximum number of archive log files to keep when log rolling is active. When the log file rolls, archive logs are renamed with an incrementing numerical extension until the max is reached. Archive log files beyond the maximum are deleted. Ignored if -g is not specified. Valid values are 1-1000. Default is 5. .TP .B \-B buf_size The size in bytes of the UDP send buffer and receive buffer to use. Valid values are 65536-104857600 (64KB-100MB). Defaults to 262144. .TP .B \-L logfile Specifies the log file. Default is /tmp/uftpproxyd.log for UNIX-like systems systems, C:\\uftpproxyd_log.txt for Windows. .TP .B \-Q dscp Specifies the Differentiated Services Code Point (DSCP), formerly Type of Service (TOS), in the IP header for all outgoing packets. Valid values are 0-63 and may be specified in either decimal or hexadecimal. Default is 0. On Windows XP systems, the OS doesn\(aqt allow this parameter to be changed by default. To change this, add/modify the following DWORD registry value, set to 0, and reboot: HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DisableUserTOSSetting Not currently supported on Windows Vista or later. .TP .B \-P pidfile The pidfile to write the daemon\(aqs pid to on startup. Default is no pidfile. .TP .B \-C clientlist_file A file containing a list of clients the proxy will allow to receive files from. The file should contain the name/IP of a client followed by the client\(aqs public key fingerprint, with one on each line. The key specified by the client must match the fingerprint. Applies only to client proxies. .nf Example contents: 0x00001111|66:1E:C9:1D:FC:99:DB:60:B0:1A:F0:8F:CA:F4:28:27:A6:BE:94:BC 0x00002222 .fi .TP .B \-S serverlist_file A file containing a list of servers. The file should contain the ID of the server, the IP address the proxy expects the server\(aqs request to come from, and optionally the server\(aqs public key fingerprint, with one entry for a server on each line. For client proxies, this is the list of servers the proxy will allow to connect, and the key specified by the server must match the fingerprint. For server proxies, if your system supports source specific multicast (SSM), the proxy will subscribe to all public and private multicast addresses using SSM for all servers listed. Response proxies perform both of the above functions When this option is specified, the public and private addresses specified by the server must be valid SSM addresses. Any ANNOUNCE that specifies a private IP that is not a valid SSM address will be rejected. Valid SSM addresses are in the 232/8 range for IPv4 and the ff30::/96 range for IPv6. .nf Example contents: 0x11112222|192.168.1.101|66:1E:C9:1D:FC:99:DB:60:B0:1A:F0:8F:CA:F4:28:27:A6:BE:94:BC 0x11113333|fe80::213:72ff:fed6:69ca .fi .TP .B \-k keyfile[,keyfile...] .TP .B \-K rsa:key_length | ec:curve[,rsa:key_length | ec:curve...] These two options are used to read and/or write the proxy\(aqs RSA/ECDSA private keys. The -K option creates one or more RSA or ECDSA private keys. New keys are specified as either rsa:key_length, which creates an RSA private key key_length bits wide, or as ec:curve, which creates an EC key using the curve "curve". The list of supported EC curves is as follows (availability may vary depending on system settings and crypto library used): sect163k1 sect163r1 sect163r2 sect193r1 sect193r2 sect233k1 sect233r1 sect239k1 sect283k1 sect283r1 sect409k1 sect409r1 sect571k1 sect571r1 secp160k1 secp160r1 secp160r2 secp192k1 prime192v1 secp224k1 secp224r1 secp256k1 prime256v1 secp384r1 secp521r1 If only -K is specified, the keys created are not persisted. If only -k is specified, this option reads RSA or ECDSA private keys from each keyfile. If -k and -K are specified, the keys created by -K are written to the keyfiles listed by -k. In this case, -k and -K must give the same number of items. If neither -k nor -K are specified, an RSA private key 512 bytes in length is generated and not persisted. If -k is specified but not -K, the RSA or ECDSA private keys are read from each keyfile. The definition of keyfile is dependent on the crypto library UFTP is compiled to use. On Windows systems, UFTP can built to use either CNG, which is the new API supported by Windows Vista and Windows 7, or CryptoAPI, which is the legacy API and the only one available to Windows XP. Under CryptoAPI, all RSA private keys must be stored in a key container (technically only keys used to sign data, but for UFTP\(aqs purposes this is the case). Key containers are internal to Windows, and each user (and the system) has its own set of key containers. In this case, key_file is actually the name of the key container. When -k is not specified, the generated key is not persisted. Elliptic Curve algorithms are not supported under CryptoAPI. Under CNG, RSA and ECDSA private keys are also stored in key containers, and RSA keys created by CrypoAPI may be read by CNG. Like CryptoAPI, key_file also specifies the key container name, and the generated key is not persisted if -k is not specified. CNG only supports 3 named EC curves: prime256v1, secp384r1, and secp521r1. All other systems use OpenSSL for the crypto library (although under Windows UFTP can be also be built to use it). In this case, key_file specifies a file name where the RSA private key is stored unencrypted in PEM format (the OS is expected to protect this file). When both -k and -K are specified, the file is only written to if it does not currently exist. If the file does exist, an error message will be returned and the server will exit. When -k is not specified, the generated key is not persisted. These PEM files may also be manipulated via the openssl(1) command line tool. Keys can also be generated and viewed via the uftp_keymgt(1) utility. .TP .B \-e ecdh_curve Specifies the EC curve type to use for a response proxy\(aqs ECDH private key. This option MUST be specified for a response proxy to use an ECDH key exchange scheme. If unspecified, no ECDH key will be created. Ignored if -r is not specified. .TP .B \-I interface[,interface...] For server proxies, lists one or more interfaces to listen to multicast traffic on. For client proxies, the interface it reports itself as to servers and clients. Interfaces can be specified either by interface name, by hostname, or by IP. When receiving a closed group membership request, the client proxy will participate if any of these interfaces matches an IP in the announcement. The default is to listen on all active non-loopback interfaces. NOTE: Since Windows doesn\(aqt have named interfaces (not in the sense that UNIX-like systems do), only hostnames or IP addresses are accepted on Windows. .TP .B \-M pub_multicast_addr[,pub_multicast_addr...] The list of public multicast addresses to listen on. Used only by server proxies. Default is 230.4.4.1 .SH SEE ALSO uftp(1), uftpd(1), uftp_keymgt(1) .SH NOTES The latest version of UFTP can be found at http://uftp-multicast.sourceforge.net. UFTP is covered by the GNU General Public License. Commercial licenses and support are available from Dennis Bush (bush@tcnj.edu). uftp-4.1.5/server_send.c0000644000076400007640000005216512250244021014167 0ustar dbushdbush/* * UFTP - UDP based FTP with multicast * * Copyright (C) 2001-2013 Dennis A. Bush, Jr. bush@tcnj.edu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7 * * If you modify this program, or any covered work, by linking or * combining it with the OpenSSL project's OpenSSL library (or a * modified version of that library), containing parts covered by the * terms of the OpenSSL or SSLeay licenses, the copyright holder * grants you additional permission to convey the resulting work. * Corresponding Source for a non-source form of such a combination * shall include the source code for the parts of OpenSSL used as well * as that of the covered work. */ #include #include #include #include #include #include #include #include #ifdef WINDOWS #include #else // if WINDOWS #include #include #include #include #include #include #endif #include "server.h" #include "server_send.h" #include "server_phase.h" #include "server_common.h" /** * Checks to see if a file/directory is in the exclude list */ int file_excluded(const char *filename) { int found, i; for (found = 0, i = 0; (i < excludecount) && !found; i++) { if (!strcmp(filename, exclude[i])) { found = 1; } } return found; } /** * Performs the send for a particular file/directory. If a directory is * specified, get the list of files and call recursively for each. * Returns 0 if a file was sent and none received it, 1 otherwise */ int send_file(const char *f_basedir, const char *filename, const char *n_destfname, uint32_t group_id, uint8_t group_inst, int delete, int freespace) { static uint16_t file_id = 1; struct finfo_t finfo; stat_struct statbuf; char path[MAXPATHNAME], destpath[MAXPATHNAME]; int len, rval, fd, emptydir, maxsecsize; log1(group_id, 0, "----- %s -----", filename); len = snprintf(path, sizeof(path), "%s%c%s", f_basedir, PATH_SEP, filename); if ((len >= sizeof(path)) || (len == -1)) { log1(group_id, 0, "Max pathname length exceeded: %s%c%s", f_basedir, PATH_SEP, filename); return 1; } if (!delete && !freespace) { if (follow_links) { rval = stat_func(path, &statbuf); } else { rval = lstat_func(path, &statbuf); } if (rval == -1) { syserror(group_id, 0, "Error getting file status for %s", filename); return 1; } } if (file_excluded(filename)) { log0(group_id, 0, "Skipping %s", filename); return 1; } rval = 1; if (freespace) { memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_FREESPACE; finfo.basedir = f_basedir; finfo.filename = n_destfname; finfo.destfname = n_destfname; finfo.group_id = group_id; finfo.group_inst = group_inst; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.deststate = safe_calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } free(finfo.deststate); } else if (delete) { memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_DELETE; finfo.basedir = f_basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.group_id = group_id; finfo.group_inst = group_inst; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.deststate = safe_calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } free(finfo.deststate); } else if (S_ISREG(statbuf.st_mode)) { if ((fd = open(path, OPENREAD, 0)) == -1) { syserror(group_id, 0, "Error reading file %s", filename); return 1; } close(fd); memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_REG; finfo.basedir = f_basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.group_id = group_id; finfo.group_inst = group_inst; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.size = statbuf.st_size; finfo.tstamp = statbuf.st_mtime; maxsecsize = (blocksize * 8 > MAXSECTION ? MAXSECTION : blocksize * 8); if (finfo.size) { finfo.blocks = (int32_t)((finfo.size / blocksize) + (finfo.size % blocksize ? 1 : 0)); finfo.sections = (finfo.blocks / maxsecsize) + (finfo.blocks % maxsecsize ? 1 : 0); finfo.secsize_small = finfo.blocks / finfo.sections; finfo.secsize_big = finfo.secsize_small + (finfo.blocks % finfo.sections ? 1 : 0); finfo.big_sections = finfo.blocks - (finfo.secsize_small * finfo.sections); } else { finfo.blocks = 0; finfo.sections = 0; finfo.secsize_small = 0; finfo.secsize_big = 0; finfo.big_sections = 0; } finfo.naklist = safe_calloc(finfo.blocks, 1); finfo.deststate = safe_calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); finfo.partial = 1; rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } free(finfo.deststate); free(finfo.naklist); #ifndef WINDOWS } else if (S_ISLNK(statbuf.st_mode)) { char linkname[MAXPATHNAME]; memset(linkname, 0, sizeof(linkname)); if (readlink(path, linkname, sizeof(linkname)-1) == -1) { syserror(group_id, 0, "Failed to read symbolic link %s", path); return 1; } // Both the file name and the link have to fit into a fileinfo_h.name if (strlen(linkname) + strlen(filename) + 2 > MAXPATHNAME) { log0(group_id, 0, "Combined file name %s and link %s too long", filename, linkname); return 1; } memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_LINK; finfo.basedir = f_basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.linkname = linkname; finfo.group_id = group_id; finfo.group_inst = group_inst; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.deststate = safe_calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); finfo.partial = 1; rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } free(finfo.deststate); #endif } else if (S_ISDIR(statbuf.st_mode)) { // read directory and do recursive send #ifdef WINDOWS intptr_t ffhandle; struct _finddatai64_t ffinfo; char dirglob[MAXPATHNAME]; snprintf(dirglob, sizeof(dirglob), "%s%c%s%c*", f_basedir, PATH_SEP, filename, PATH_SEP); if ((ffhandle = _findfirsti64(dirglob, &ffinfo)) == -1) { syserror(group_id, 0, "Failed to open directory %s%c%s", f_basedir, PATH_SEP, filename); return 1; } emptydir = 1; do { len = snprintf(path, sizeof(path), "%s/%s", filename, ffinfo.name); log3(group_id, 0, "Checking file %s", path); if ((len >= sizeof(path)) || (len == -1)) { log0(group_id, 0, "Max pathname length exceeded: %s/%s", filename, ffinfo.name); continue; } len = snprintf(destpath, sizeof(destpath), "%s/%s", n_destfname, ffinfo.name); if ((len >= sizeof(destpath)) || (len == -1)) { log0(group_id, 0, "Max pathname length exceeded: %s/%s", n_destfname, ffinfo.name); continue; } if (strcmp(ffinfo.name, ".") && strcmp(ffinfo.name, "..")) { emptydir = 0; if (!send_file(f_basedir, path, destpath, group_id, group_inst, 0, 0)) { rval = 0; break; } } } while (_findnexti64(ffhandle, &ffinfo) == 0); _findclose(ffhandle); #else DIR *dir; struct dirent *de; char dirname[MAXPATHNAME]; snprintf(dirname, sizeof(dirname), "%s%c%s", f_basedir, PATH_SEP, filename); if ((dir = opendir(dirname)) == NULL) { syserror(group_id, 0, "Failed to open directory %s", dirname); return 1; } // errno needs to be set to 0 before calling readdir, otherwise // we'll report a false error when we exhaust the directory emptydir = 1; while ((errno = 0, de = readdir(dir)) != NULL) { len = snprintf(path, sizeof(path), "%s/%s", filename, de->d_name); if ((len >= sizeof(path)) || (len == -1)) { log0(group_id, 0, "Max pathname length exceeded: %s/%s", filename, de->d_name); continue; } len = snprintf(destpath, sizeof(destpath), "%s/%s", n_destfname, de->d_name); if ((len >= sizeof(destpath)) || (len == -1)) { log0(group_id, 0, "Max pathname length exceeded: %s/%s", n_destfname, de->d_name); continue; } if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { emptydir = 0; if (!send_file(f_basedir, path, destpath, group_id, group_inst, 0, 0)) { rval = 0; break; } } } if (errno && (errno != ENOENT)) { syserror(group_id, 0, "Failed to read directory %s", filename); } closedir(dir); #endif if (emptydir) { memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_DIR; finfo.basedir = f_basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.group_id = group_id; finfo.group_inst = group_inst; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.deststate = safe_calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); finfo.partial = 1; rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } free(finfo.deststate); } } else { log0(group_id, 0, "Skipping special file %s", filename); } return rval; } /** * Write a restart file entry for a particular client. * Returns 1 on success, o on fail. */ int write_restart_host(uint32_t group_id, int fd, int i) { struct server_restart_host_t host; memset(&host, 0, sizeof(host)); strncpy(host.name, destlist[i].name, sizeof(host.name)); host.name[sizeof(host.name)-1] = '\x0'; host.id = destlist[i].id; if (destlist[i].has_fingerprint) { host.has_fingerprint = 1; memcpy(host.keyfingerprint, destlist[i].keyfingerprint, HMAC_LEN); } host.is_proxy = (destlist[i].clientcnt != -1); if (file_write(fd, &host, sizeof(host)) == -1) { log0(group_id, 0, "Failed to write host for restart file"); return 0; } return 1; } /** * Save the state of a failed transfer so it can restarted later. */ void write_restart_file(uint32_t group_id, uint8_t group_inst) { struct server_restart_t header; char restart_name[MAXFILENAME]; char proxy_listed[MAXPROXYDEST]; int fd, opened, i, j, proxycnt, found; memset(proxy_listed, 0, sizeof(proxy_listed)); opened = 0; proxycnt = 0; for (i = 0; i < destcount; i++) { if ((destlist[i].clientcnt == -1) && client_error(i)) { if (!opened) { snprintf(restart_name, sizeof(restart_name), "_group_%08X_restart", group_id); if ((fd = open(restart_name, OPENWRITE | O_CREAT | O_TRUNC, 0644)) == -1) { syserror(group_id, 0, "Failed to create restart file"); return; } // Write header header.group_id = group_id; header.group_inst = group_inst; header.filecount = filecount; if (file_write(fd, &header, sizeof(header)) == -1) { log0(group_id,0, "Failed to write header for restart file"); goto errexit; } // Write file list for (j = 0; j < filecount; j++) { if (file_write(fd, filelist[j],sizeof(filelist[j])) == -1) { log0(group_id, 0, "Failed to write filename for restart file"); goto errexit; } } opened = 1; } if (!write_restart_host(group_id, fd, i)) { goto errexit; } if (destlist[i].proxyidx != -1) { for (j = 0, found = 0; (j < proxycnt) && !found; j++) { if (proxy_listed[j] == destlist[i].proxyidx) { found = 1; } } if (!found) { if (!write_restart_host(group_id, fd, destlist[i].proxyidx)) { goto errexit; } proxy_listed[proxycnt++] = destlist[i].proxyidx; } } } } if (opened) { close(fd); } return; errexit: close(fd); unlink(restart_name); } /** * The main sending function. Goes through all files/diectories specified on * the command line and initializes the group for multiple files. */ int send_files(void) { int i, j, rval, len, found_base, delete; struct finfo_t group_info; char *dir, *base; time_t t; int (*ncmp)(const char *, const char *, size_t); char path[MAXPATHNAME], l_destfname[MAXPATHNAME], mcast[INET6_ADDRSTRLEN]; #ifdef WINDOWS ncmp = strnicmp; #else ncmp = strncmp; #endif memset(&group_info, 0, sizeof(struct finfo_t)); if (restart_groupid) { group_info.group_id = restart_groupid; group_info.group_inst = restart_groupinst + 1; } else { group_info.group_id = rand32(); group_info.group_inst = 0; } group_info.deststate = safe_calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); t = time(NULL); if (!showtime) slog0(""); log0(group_info.group_id, 0, "%s", VERSIONSTR); if (!showtime) clog0(group_info.group_id, 0, "Starting at %s", ctime(&t)); if (privkey.key) { if ((keyextype == KEYEX_RSA) || (keyextype == KEYEX_ECDH_RSA)) { log1(group_info.group_id, 0, "Loaded %d bit RSA key with fingerprint %s", RSA_keylen(privkey.rsa) * 8, print_key_fingerprint(privkey, KEYBLOB_RSA)); } else { log1(group_info.group_id, 0, "Loaded ECDSA key with curve %s and fingerprint %s", curve_name(get_EC_curve(privkey.ec)), print_key_fingerprint(privkey, KEYBLOB_EC)); } } if (dhkey.key) { log1(group_info.group_id, 0, "Loaded ECDH key with curve %s", curve_name(get_EC_curve(dhkey.ec))); } if (cc_type == CC_NONE || cc_type == CC_UFTP3) { if (rate == -1) { log2(group_info.group_id, 0, "Transfer rate: full interface speed"); } else { log2(group_info.group_id, 0, "Transfer rate: %d Kbps (%d KB/s)", rate * 8 / 1024, rate / 1024); log2(group_info.group_id, 0, "Wait between packets: %d us", packet_wait); } } else if (cc_type == CC_TFMCC) { log2(group_info.group_id, 0, "Transfer rate: dynamic via TFMCC"); } if (log_level >= 2) { rval = getnameinfo((struct sockaddr *)&receive_dest, family_len(receive_dest), mcast, sizeof(mcast), NULL, 0, NI_NUMERICHOST); if (rval) { log2(group_info.group_id, 0, "getnameinfo failed: %s", gai_strerror(rval)); } log2(group_info.group_id, 0, "Using private multicast address %s " "Group ID: %08X", mcast, group_info.group_id); } rval = announce_phase(&group_info); if (rval) { rval = 0; for (i = 0; i < filecount; i++) { if (!strcmp(filelist[i], "@FREESPACE")) { rval = send_file(".", ".", ".", group_info.group_id, group_info.group_inst, 0, 1); if (!rval) { break; } continue; } if (!strncmp(filelist[i], "@DELETE:", 8)) { split_path(&filelist[i][8], &dir, &base); delete = 1; } else { split_path(filelist[i], &dir, &base); delete = 0; } if (basedircount > 0) { for (found_base = 0, j = 0; j < basedircount; j++) { if (!ncmp(basedir[j],filelist[i], strlen(basedir[j]))) { found_base = 1; break; } } if (!found_base) { log0(group_info.group_id, 0, "Skipping %s: " "doesn't match any base", filelist[i]); free(dir); free(base); continue; } strncpy(l_destfname, filelist[i] + strlen(basedir[j]), sizeof(l_destfname)-1); } else { strncpy(l_destfname, base, sizeof(l_destfname)-1); } #if PATH_SEP != '/' // Translate any PATH_SEP in the sent file name to '/' { char *p; while ((p = strchr(l_destfname, PATH_SEP)) != NULL) { *p = '/'; } } #endif if (strcmp(destfname, "")) { if ((filecount > 1) || dest_is_dir) { len = snprintf(path, sizeof(path), "%s/%s", destfname, l_destfname); if ((len >= sizeof(path)) || (len == -1)) { log0(group_info.group_id, 0, "Max pathname length " "exceeded: %s/%s", destfname, base); free(dir); free(base); continue; } rval = send_file(dir, base, path, group_info.group_id, group_info.group_inst, delete, 0); } else { rval = send_file(dir, base, destfname, group_info.group_id, group_info.group_inst, delete, 0); } } else { rval = send_file(dir, base, l_destfname, group_info.group_id, group_info.group_inst, delete, 0); } free(dir); free(base); if (!rval) { break; } } if (rval) { log1(group_info.group_id, 0, "-----------------------------"); completion_phase(&group_info); } } if (save_fail) { write_restart_file(group_info.group_id, group_info.group_inst); } free(group_info.deststate); t = time(NULL); if (!showtime) clog0(group_info.group_id, 0, "uftp: Finishing at %s", ctime(&t)); return rval; }