diff --git a/airport.pem b/airport.pem new file mode 100644 index 00000000..381ff167 --- /dev/null +++ b/airport.pem @@ -0,0 +1,24 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt +wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U +wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf +/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/ +UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW +BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa +LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5 +NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm +lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz +aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu +a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM +oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z +oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+ +k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL +AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA +cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf +54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov +17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc +1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI +LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ +2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKaXTyY= +-----END RSA PRIVATE KEY----- + diff --git a/shairport.c b/shairport.c new file mode 100644 index 00000000..144bc0a8 --- /dev/null +++ b/shairport.c @@ -0,0 +1,1141 @@ +/* + * ShairPort Initializer - Network Initializer/Mgr for Hairtunes RAOP + * Copyright (c) M. Andrew Webster 2011 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "fcntl.h" +#include "socketlib.h" +#include "shairport.h" +#include "hairtunes.c" // couldn't figure out how to allow both mains. + +// TEMP + +int kCurrentLogLevel = LOG_INFO; + +#ifdef _WIN32 +#define DEVNULL "nul" +#else +#define DEVNULL "/dev/null" +#endif +#define RSA_LOG_LEVEL LOG_DEBUG_VV +#define SOCKET_LOG_LEVEL LOG_DEBUG_VV +#define HEADER_LOG_LEVEL LOG_DEBUG +#define AVAHI_LOG_LEVEL LOG_DEBUG + +int main(int argc, char **argv) +{ + char tHWID[HWID_SIZE] = {0,51,52,53,54,55}; + char tHWID_Hex[HWID_SIZE * 2]; + + char tServerName[56] = "ShairPort"; + char tPassword[56] = ""; + + struct addrinfo *tAddrInfo; + int tSimLevel = 0; + int tUseKnownHWID = FALSE; + int tDaemonize = FALSE; + int tPort = PORT; + + char *arg; + while ( (arg = *++argv) ) { + if(!strcmp(arg, "-a")) + { + strncpy(tServerName, *++argv, 55); + argc--; + } + else if(!strcmp(arg, "--apname=")) + { + strncpy(tServerName, arg+9, 55); + } + else if(!strcmp(arg, "-p")) + { + strncpy(tPassword, *++argv, 55); + argc--; + } + else if(!strcmp(arg, "--password=")) + { + strncpy(tPassword, arg+11, 55); + } + else if(!strcmp(arg, "-o")) + { + tPort = atoi(*++argv); + argc--; + } + else if(!strcmp(arg, "--server_port=")) + { + tPort = atoi(arg+14); + } + else if(!strcmp(arg, "-k")) + { + tUseKnownHWID = TRUE; + } + else if(!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) + { + kCurrentLogLevel = 0; + } + else if(!strcmp(arg, "-d")) + { + tDaemonize = TRUE; + kCurrentLogLevel = 0; + } + else if(!strcmp(arg, "-v")) + { + kCurrentLogLevel = LOG_DEBUG; + } + else if(!strcmp(arg, "-v2")) + { + kCurrentLogLevel = LOG_DEBUG_V; + } + else if(!strcmp(arg, "-vv") || !strcmp(arg, "-v3")) + { + kCurrentLogLevel = LOG_DEBUG_VV; + } + else if(!strcmp(arg, "-h") || !strcmp(arg, "--help")) + { + slog(LOG_INFO, "ShairPort version 0.05 C port - Airport Express emulator\n"); + slog(LOG_INFO, "Usage:\nshairport [OPTION...]\n\nOptions:\n"); + slog(LOG_INFO, " -a, --apname=AirPort Sets Airport name\n"); + slog(LOG_INFO, " -p, --password=secret Sets Password (not working)\n"); + slog(LOG_INFO, " -o, --server_port=5000 Sets Port for Avahi/dns-sd\n"); + slog(LOG_INFO, " -d Daemon mode\n"); + slog(LOG_INFO, " -q, --quiet Supresses all output.\n"); + slog(LOG_INFO, " -v,-v2,-v3,-vv Various debugging levels\n"); + slog(LOG_INFO, "\n"); + return 0; + } + } + + if(tDaemonize) + { + int tPid = fork(); + if(tPid < 0) + { + exit(1); // Error on fork + } + else if(tPid > 0) + { + exit(0); + } + else + { + setsid(); + int tIdx = 0; + for(tIdx = getdtablesize(); tIdx >= 0; --tIdx) + { + close(tIdx); + } + tIdx = open(DEVNULL, O_RDWR); + dup(tIdx); + dup(tIdx); + } + } + srand ( time(NULL) ); + // Copy over empty 00's + //tPrintHWID[tIdx] = tAddr[0]; + + int tIdx = 0; + for(tIdx=0;tIdx 0) + { + if(!tUseKnownHWID) + { + int tVal = ((random() % 80) + 33); + tHWID[tIdx] = tVal; + } + //tPrintHWID[tIdx] = tAddr[tIdx]; + } + sprintf(tHWID_Hex+(tIdx*2), "%02X",tHWID[tIdx]); + } + //tPrintHWID[HWID_SIZE] = '\0'; + + slog(LOG_INFO, "LogLevel: %d\n", kCurrentLogLevel); + slog(LOG_INFO, "AirName: %s\n", tServerName); + slog(LOG_INFO, "HWID: %.*s\n", HWID_SIZE, tHWID+1); + slog(LOG_INFO, "HWID_Hex(%d): %s\n", strlen(tHWID_Hex), tHWID_Hex); + + if(tSimLevel >= 1) + { + #ifdef SIM_INCL + sim(tSimLevel, tTestValue, tHWID); + #endif + return(1); + } + else + { + startAvahi(tHWID_Hex, tServerName, tPort); + slog(LOG_DEBUG_V, "Starting connection server: specified server port: %d\n", tPort); + int tServerSock = setupListenServer(&tAddrInfo, tPort); + if(tServerSock < 0) + { + freeaddrinfo(tAddrInfo); + slog(LOG_INFO, "Error setting up server socket on port %d, try specifying a different port\n", tPort); + exit(1); + } + + int tClientSock = 0; + while(1) + { + slog(LOG_DEBUG_V, "Waiting for clients to connect\n"); + tClientSock = acceptClient(tServerSock, tAddrInfo); + if(tClientSock > 0) + { + int tPid = fork(); + if(tPid == 0) + { + freeaddrinfo(tAddrInfo); + tAddrInfo = NULL; + slog(LOG_DEBUG, "...Accepted Client Connection..\n"); + close(tServerSock); + handleClient(tClientSock, tPassword, tHWID); + //close(tClientSock); + return 0; + } + else + { + slog(LOG_DEBUG_VV, "Child now busy handling new client\n"); + close(tClientSock); + } + } + else + { + // failed to init server socket....try waiting a moment... + sleep(2); + } + } + } + + slog(LOG_DEBUG_VV, "Finished, and waiting to clean everything up\n"); + sleep(1); + if(tAddrInfo != NULL) + { + freeaddrinfo(tAddrInfo); + } + return 0; +} + +int findEnd(char *tReadBuf) +{ + // find \n\n, \r\n\r\n, or \r\r is found + int tIdx = 0; + int tLen = strlen(tReadBuf); + for(tIdx = 0; tIdx < tLen; tIdx++) + { + if(tReadBuf[tIdx] == '\r') + { + if(tIdx + 1 < tLen) + { + if(tReadBuf[tIdx+1] == '\r') + { + return (tIdx+1); + } + else if(tIdx+3 < tLen) + { + if(tReadBuf[tIdx+1] == '\n' && + tReadBuf[tIdx+2] == '\r' && + tReadBuf[tIdx+3] == '\n') + { + return (tIdx+3); + } + } + } + } + else if(tReadBuf[tIdx] == '\n') + { + if(tIdx + 1 < tLen && tReadBuf[tIdx+1] == '\n') + { + return (tIdx + 1); + } + } + } + // Found nothing + return -1; +} + +void handleClient(int pSock, char *pPassword, char *pHWADDR) +{ + slog(LOG_DEBUG_VV, "In Handle Client\n"); + fflush(stdout); + + socklen_t len; + struct sockaddr_storage addr; + #ifdef AF_INET6 + char ipstr[INET6_ADDRSTRLEN]; + #else + char ipstr[INET_ADDRSTRLEN]; + #endif + int port; + + len = sizeof addr; + getsockname(pSock, (struct sockaddr*)&addr, &len); + + // deal with both IPv4 and IPv6: + if (addr.ss_family == AF_INET) { + slog(LOG_DEBUG_V, "Constructing ipv4 address\n"); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + port = ntohs(s->sin_port); + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + } else { // AF_INET6 + slog(LOG_DEBUG_V, "Constructing ipv6 address\n"); + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; + port = ntohs(s->sin6_port); + inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); + } + + slog(LOG_DEBUG_V, "Peer IP address: %s\n", ipstr); + slog(LOG_DEBUG_V, "Peer port : %d\n", port); + + int tMoreDataNeeded = TRUE; + struct keyring tKeys; + struct comms tComms; + struct connection tConn; + initConnection(&tConn, &tKeys, &tComms, pSock, pPassword); + + while(1) + { + tMoreDataNeeded = TRUE; + + initBuffer(&tConn.recv, 80); // Just a random, small size to seed the buffer with. + initBuffer(&tConn.resp, 80); + + int tError = FALSE; + while(TRUE == tMoreDataNeeded) + { + tError = readDataFromClient(pSock, &(tConn.recv)); + if(!tError) + { + slog(LOG_DEBUG_VV, "Finished Reading some data from client\n"); + // parse client request + tMoreDataNeeded = parseMessage(&tConn, ipstr, pHWADDR); + if(TRUE == tMoreDataNeeded) + { + slog(LOG_DEBUG_VV, "\n\nNeed to read more data\n"); + } + else if(-1 == tMoreDataNeeded) // Forked process down below ended. + { + slog(LOG_DEBUG_V, "Forked Process ended...cleaning up\n"); + cleanup(&tConn); + // pSock was already closed + return; + } + // if more data needed, + } + else + { + slog(LOG_DEBUG, "Error reading from socket, closing client\n"); + // Error reading data....quit. + cleanup(&tConn); + return; + } + } + slog(LOG_DEBUG_VV, "Writing: %d chars to socket\n", tConn.resp.current); + //tConn->resp.data[tConn->resp.current-1] = '\0'; + writeDataToClient(pSock, &(tConn.resp)); + // Finished reading one message... + cleanupBuffers(&tConn); + } + cleanup(&tConn); + fflush(stdout); +} + + +void writeDataToClient(int pSock, struct shairbuffer *pResponse) +{ + slog(LOG_DEBUG_VV, "\n----Beg Send Response Header----\n%.*s\n", pResponse->current, pResponse->data); + send(pSock, pResponse->data, pResponse->current,0); + slog(LOG_DEBUG_VV, "----Send Response Header----\n"); +} + +void convertIpToBinary(char *tSockethost, int pIsIPv6, char* pIpBinary) +{ + if(pIsIPv6) + { + // do something different + slog(LOG_INFO, " Must implement ipv6 signature portion in convertIpToBinary...sorry.\n"); + } + else + { + // Takes care of ipv4 addresses like ::ffff:192.168.1.3 + char *tStartOfV4 = strrchr(tSockethost, ':'); + if(NULL == tStartOfV4) + { + tStartOfV4 = tSockethost; + } + else + { + tStartOfV4+=1; + } + + char tIp[strlen(tStartOfV4)+1]; + memset(tIp, 0, (strlen(tStartOfV4)+1)); + strncpy(tIp, tStartOfV4, strlen(tStartOfV4)); + + char *tIpTokens = strtok (tIp, "."); + + int tIdx = 0; + // if ipv4, discard prefill + while(tIpTokens != NULL) + { + slog(LOG_DEBUG_VV, "%s\n", tIpTokens); + pIpBinary[tIdx++] = atoi(tIpTokens); + tIpTokens = strtok(NULL, " ."); + } + } +} + +int readDataFromClient(int pSock, struct shairbuffer *pClientBuffer) +{ + char tReadBuf[MAX_SIZE]; + + int tRetval = TRUE; + int tEnd = -1; + while(tRetval > 0 && tEnd < 0) + { + // Read from socket until \n\n, \r\n\r\n, or \r\r is found + slog(LOG_DEBUG_V, "Waiting To Read...\n"); + fflush(stdout); + tRetval = read(pSock, tReadBuf, MAX_SIZE); + // if new buffer contains the end of request string, only copy partial buffer? + tEnd = findEnd(tReadBuf); + if(tEnd >= 0) + { + if(pClientBuffer->marker == 0) + { + pClientBuffer->marker = tEnd+1; // Marks start of content + } + slog(SOCKET_LOG_LEVEL, "Found end of http request at: %d\n", tEnd); + fflush(stdout); + } + else + { + tEnd = MAX_SIZE; + slog(SOCKET_LOG_LEVEL, "Read %d of data so far\n%s\n", tRetval, tReadBuf); + fflush(stdout); + } + if(tRetval > 0) + { + // Copy read data into tReceive; + slog(SOCKET_LOG_LEVEL, "Read %d data, using %d of it\n", tRetval, tEnd); + addNToShairBuffer(pClientBuffer, tReadBuf, tRetval); + slog(LOG_DEBUG_VV, "Finished copying data\n"); + } + else + { + slog(LOG_DEBUG, "Error reading data from socket.\n"); + return tRetval; + } + } + if(tEnd + 1 != tRetval) + { + slog(SOCKET_LOG_LEVEL, "Read more data after end of http request. %d instead of %d\n", tRetval, tEnd+1); + } + slog(SOCKET_LOG_LEVEL, "Finished Reading Data:\n%s\nEndOfData\n", pClientBuffer->data); + fflush(stdout); + return 0; +} + +char *getFromBuffer(char *pBufferPtr, const char *pField, int pLenAfterField, int *pReturnSize, char *pDelims) +{ + slog(LOG_DEBUG_V, "GettingFromBuffer: %s\n", pField); + char* tFound = strstr(pBufferPtr, pField); + int tSize = 0; + if(tFound != NULL) + { + tFound += (strlen(pField) + pLenAfterField); + int tIdx = 0; + char tDelim = pDelims[tIdx]; + char *tShortest = NULL; + char *tEnd = NULL; + while(tDelim != '\0') + { + tDelim = pDelims[tIdx++]; // Ensures that \0 is also searched. + tEnd = strchr(tFound, tDelim); + if(tEnd != NULL && (NULL == tShortest || tEnd < tShortest)) + { + tShortest = tEnd; + } + } + + tSize = (int) (tShortest - tFound); + slog(LOG_DEBUG_VV, "Found %.*s length: %d\n", tSize, tFound, tSize); + if(pReturnSize != NULL) + { + *pReturnSize = tSize; + } + } + else + { + slog(LOG_DEBUG_V, "Not Found\n"); + } + return tFound; +} + + +char *getFromHeader(char *pHeaderPtr, const char *pField, int *pReturnSize) +{ + return getFromBuffer(pHeaderPtr, pField, 2, pReturnSize, "\r\n"); +} + +char *getFromContent(char *pContentPtr, const char* pField, int *pReturnSize) +{ + return getFromBuffer(pContentPtr, pField, 1, pReturnSize, "\r\n"); +} + +char *getFromSetup(char *pContentPtr, const char* pField, int *pReturnSize) +{ + return getFromBuffer(pContentPtr, pField, 1, pReturnSize, ";\r\n"); +} + +// Handles compiling the Apple-Challenge, HWID, and Server IP Address +// Into the response the airplay client is expecting. +int buildAppleResponse(struct connection *pConn, char *pIpStr, char *pHWID) +{ + // Find Apple-Challenge + char *tResponse = NULL; + + int tFoundSize = 0; + char* tFound = getFromHeader(pConn->recv.data, "Apple-Challenge", &tFoundSize); + + if(tFound != NULL) + { + char tTrim[tFoundSize + 2]; + getTrimmed(tFound, tFoundSize, TRUE, TRUE, tTrim); + slog(LOG_DEBUG_VV, "HeaderChallenge: [%s] len: %d sizeFound: %d\n", tTrim, strlen(tTrim), tFoundSize); + int tChallengeDecodeSize = 16; + char *tTmp = NULL; + char *tChallenge = decode_base64((unsigned char *)tTrim, tFoundSize, &tChallengeDecodeSize); + slog(LOG_DEBUG_VV, "Challenge Decode size: %d expected 16\n", tChallengeDecodeSize); + + char tIpAddress[4]; + convertIpToBinary(pIpStr, FALSE, tIpAddress); + int tIpSize = 4; + if(FALSE) + { + // TODO - Handle/Test on IPV6 + tIpSize = 6; // IPV6 + } + + tTmp = encode_base64((unsigned char *) tIpAddress, tIpSize); + slog(LOG_DEBUG_VV, "IPDATA: %s => %s\n", pIpStr, tTmp); + free(tTmp); + + int tCurSize = 0; + unsigned char tChalResp[32]; + + memcpy(tChalResp, tChallenge, tChallengeDecodeSize); + tCurSize += tChallengeDecodeSize; + + memcpy(tChalResp+tCurSize, tIpAddress, tIpSize); + tCurSize += tIpSize; + + memcpy(tChalResp+tCurSize, pHWID, HWID_SIZE); + tCurSize += HWID_SIZE; + + int tPad = 32 - tCurSize; + memset(tChalResp+tCurSize, 0, tPad); + + tTmp = encode_base64((unsigned char *)tChalResp, 32); + slog(LOG_DEBUG_VV, "Full sig: %s\n", tTmp); + free(tTmp); + + // RSA Encrypt + RSA *rsa = loadKey(); // Free RSA + int tSize = RSA_size(rsa); + unsigned char tTo[tSize]; + RSA_private_encrypt(32, (unsigned char *)tChalResp, tTo, rsa, RSA_PKCS1_PADDING); + + // Wrap RSA Encrypted binary in Base64 encoding + tResponse = encode_base64(tTo, tSize); + int tLen = strlen(tResponse); + while(tLen > 1 && tResponse[tLen-1] == '=') + { + tResponse[tLen-1] = '\0'; + } + free(tChallenge); + RSA_free(rsa); + } + + if(tResponse != NULL) + { + // Append to current response + addToShairBuffer(&(pConn->resp), "Apple-Response: "); + addToShairBuffer(&(pConn->resp), tResponse); + addToShairBuffer(&(pConn->resp), "\n"); + free(tResponse); + return TRUE; + } + return FALSE; +} + +//parseMessage(tConn->recv.data, tConn->recv.mark, &tConn->resp, ipstr, pHWADDR, tConn->keys); +int parseMessage(struct connection *pConn, char *pIpStr, char *pHWID) +{ + int tReturn = 0; // 0 = good, 1 = Needs More Data, -1 = close client socket. + if(pConn->resp.data == NULL) + { + initBuffer(&(pConn->resp), MAX_SIZE); + } + + char *tContent = getFromHeader(pConn->recv.data, "Content-Length", NULL); + if(tContent != NULL) + { + int tContentSize = atoi(tContent); + if(pConn->recv.marker == 0 || strlen(pConn->recv.data+pConn->recv.marker) != tContentSize) + { + if(isLogEnabledFor(HEADER_LOG_LEVEL)) + { + slog(HEADER_LOG_LEVEL, "Content-Length: %s value -> %d\n", tContent, tContentSize); + if(pConn->recv.marker != 0) + { + slog(HEADER_LOG_LEVEL, "ContentPtr has %d, but needs %d\n", + strlen(pConn->recv.data+pConn->recv.marker), tContentSize); + } + } + // check if value in tContent > 2nd read from client. + return 1; // means more content-length needed + } + } + else + { + slog(LOG_DEBUG_VV, "No content, header only\n"); + } + + // "Creates" a new Response Header for our response message + addToShairBuffer(&(pConn->resp), "RTSP/1.0 200 OK\n"); + + if(isLogEnabledFor(LOG_INFO)) + { + int tLen = strchr(pConn->recv.data, ' ') - pConn->recv.data; + if(tLen < 0 || tLen > 20) + { + tLen = 20; + } + slog(LOG_INFO, "********** RECV %.*s **********\n", tLen, pConn->recv.data); + } + + if(pConn->password != NULL) + { + + } + + if(buildAppleResponse(pConn, pIpStr, pHWID)) // need to free sig + { + slog(LOG_DEBUG_V, "Added AppleResponse to Apple-Challenge request\n"); + } + + // Find option, then based on option, do different actions. + if(strncmp(pConn->recv.data, "OPTIONS", 7) == 0) + { + propogateCSeq(pConn); + addToShairBuffer(&(pConn->resp), + "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER\n"); + } + else if(!strncmp(pConn->recv.data, "ANNOUNCE", 8)) + { + char *tContent = pConn->recv.data + pConn->recv.marker; + int tSize = 0; + char *tHeaderVal = getFromContent(tContent, "a=aesiv", &tSize); // Not allocated memory, just pointing + if(tSize > 0) + { + int tKeySize = 0; + char tEncodedAesIV[tSize + 2]; + getTrimmed(tHeaderVal, tSize, TRUE, TRUE, tEncodedAesIV); + slog(LOG_DEBUG_VV, "AESIV: [%.*s] Size: %d Strlen: %d\n", tSize, tEncodedAesIV, tSize, strlen(tEncodedAesIV)); + char *tDecodedIV = decode_base64((unsigned char*) tEncodedAesIV, tSize, &tSize); + + // grab the key, copy it out of the receive buffer + tHeaderVal = getFromContent(tContent, "a=rsaaeskey", &tKeySize); + char tEncodedAesKey[tKeySize + 2]; // +1 for nl, +1 for \0 + getTrimmed(tHeaderVal, tKeySize, TRUE, TRUE, tEncodedAesKey); + slog(LOG_DEBUG_VV, "AES KEY: [%s] Size: %d Strlen: %d\n", tEncodedAesKey, tKeySize, strlen(tEncodedAesKey)); + // remove base64 coding from key + char *tDecodedAesKey = decode_base64((unsigned char*) tEncodedAesKey, + tKeySize, &tKeySize); // Need to free DecodedAesKey + + // Grab the formats + int tFmtpSize = 0; + char *tFmtp = getFromContent(tContent, "a=fmtp", &tFmtpSize); // Don't need to free + tFmtp = getTrimmedMalloc(tFmtp, tFmtpSize, TRUE, FALSE); // will need to free + slog(LOG_DEBUG_VV, "Format: %s\n", tFmtp); + + RSA *rsa = loadKey(); + // Decrypt the binary aes key + char *tDecryptedKey = malloc(RSA_size(rsa) * sizeof(char)); // Need to Free Decrypted key + //char tDecryptedKey[RSA_size(rsa)]; + if(RSA_private_decrypt(tKeySize, (unsigned char *)tDecodedAesKey, + (unsigned char*) tDecryptedKey, rsa, RSA_PKCS1_OAEP_PADDING) >= 0) + { + slog(LOG_DEBUG, "Decrypted AES key from RSA Successfully\n"); + } + else + { + slog(LOG_INFO, "Error Decrypting AES key from RSA\n"); + } + free(tDecodedAesKey); + RSA_free(rsa); + + setKeys(pConn->keys, tDecodedIV, tDecryptedKey, tFmtp); + + propogateCSeq(pConn); + } + } + else if(!strncmp(pConn->recv.data, "SETUP", 5)) + { + // Setup pipes + struct comms *tComms = pConn->hairtunes; + if (! (pipe(tComms->in) == 0 && pipe(tComms->out) == 0)) + { + slog(LOG_INFO, "Error setting up hairtunes communications...some things probably wont work very well.\n"); + } + + // Setup fork + char tPort[8] = "6000"; // get this from dup()'d stdout of child pid + + int tPid = fork(); + if(tPid == 0) + { + int tDataport=0; + char tCPortStr[8] = "59010"; + char tTPortStr[8] = "59012"; + int tSize = 0; + + char *tFound =getFromSetup(pConn->recv.data, "control_port", &tSize); + getTrimmed(tFound, tSize, 1, 0, tCPortStr); + tFound = getFromSetup(pConn->recv.data, "timing_port", &tSize); + getTrimmed(tFound, tSize, 1, 0, tTPortStr); + + slog(LOG_DEBUG_VV, "converting %s and %s from str->int\n", tCPortStr, tTPortStr); + int tControlport = atoi(tCPortStr); + int tTimingport = atoi(tTPortStr); + + slog(LOG_DEBUG_V, "Got %d for CPort and %d for TPort\n", tControlport, tTimingport); + char *tRtp = NULL; + char *tPipe = NULL; + char *tAoDriver = NULL; + char *tAoDeviceName = NULL; + char *tAoDeviceId = NULL; + + // ************************************************* + // ** Setting up Pipes, AKA no more debug/output ** + // ************************************************* + dup2(tComms->in[0],0); // Input to child + closePipe(&(tComms->in[0])); + closePipe(&(tComms->in[1])); + + dup2(tComms->out[1], 1); // Output from child + closePipe(&(tComms->out[1])); + closePipe(&(tComms->out[0])); + + struct keyring *tKeys = pConn->keys; + pConn->keys = NULL; + pConn->hairtunes = NULL; + + // Free up any recv buffers, etc.. + if(pConn->clientSocket != -1) + { + close(pConn->clientSocket); + pConn->clientSocket = -1; + } + cleanupBuffers(pConn); + hairtunes_init(tKeys->aeskey, tKeys->aesiv, tKeys->fmt, tControlport, tTimingport, + tDataport, tRtp, tPipe, tAoDriver, tAoDeviceName, tAoDeviceId); + + // Quit when finished. + slog(LOG_DEBUG, "Returned from hairtunes init....returning -1, should close out this whole side of the fork\n"); + return -1; + } + else if(tPid >0) + { + // Ensure Connection has access to the pipe. + closePipe(&(tComms->in[0])); + closePipe(&(tComms->out[1])); + + char tFromHairtunes[80]; + int tRead = read(tComms->out[0], tFromHairtunes, 80); + if(tRead <= 0) + { + slog(LOG_INFO, "Error reading port from hairtunes function, assuming default port: %d\n", tPort); + } + else + { + int tSize = 0; + char *tPortStr = getFromHeader(tFromHairtunes, "port", &tSize); + if(tPortStr != NULL) + { + getTrimmed(tPortStr, tSize, TRUE, FALSE, tPort); + } + else + { + slog(LOG_INFO, "Read %d bytes, Error translating %s into a port\n", tRead, tFromHairtunes); + } + } + // READ Ports from here?close(pConn->hairtunes_pipes[0]); + propogateCSeq(pConn); + int tSize = 0; + char *tTransport = getFromHeader(pConn->recv.data, "Transport", &tSize); + addToShairBuffer(&(pConn->resp), "Transport: "); + addNToShairBuffer(&(pConn->resp), tTransport, tSize); + // Append server port: + addToShairBuffer(&(pConn->resp), ";server_port="); + addToShairBuffer(&(pConn->resp), tPort); + addToShairBuffer(&(pConn->resp), "\nSession: DEADBEEF\n"); + } + else + { + slog(LOG_INFO, "Error forking process....dere' be errors round here.\n"); + return -1; + } + } + else if(!strncmp(pConn->recv.data, "TEARDOWN", 8)) + { + // Be smart? Do more finish up stuff... + addToShairBuffer(&(pConn->resp), "Connection: close\n"); + propogateCSeq(pConn); + close(pConn->hairtunes->in[1]); + slog(LOG_DEBUG, "Tearing down connection, closing pipes\n"); + //close(pConn->hairtunes->out[0]); + tReturn = -1; // Close client socket, but sends an ACK/OK packet first + } + else if(!strncmp(pConn->recv.data, "FLUSH", 5)) + { + write(pConn->hairtunes->in[1], "flush\n", 6); + propogateCSeq(pConn); + } + else if(!strncmp(pConn->recv.data, "SET_PARAMETER", 13)) + { + propogateCSeq(pConn); + int tSize = 0; + char *tVol = getFromHeader(pConn->recv.data, "volume", &tSize); + slog(LOG_DEBUG_VV, "About to write [vol: %.*s] data to hairtunes\n", tSize, tVol); + + write(pConn->hairtunes->in[1], "vol: ", 5); + write(pConn->hairtunes->in[1], tVol, tSize); + write(pConn->hairtunes->in[1], "\n", 1); + slog(LOG_DEBUG_VV, "Finished writing data write data to hairtunes\n"); + } + else + { + slog(LOG_DEBUG, "\n\nUn-Handled recv: %s\n", pConn->recv.data); + propogateCSeq(pConn); + } + addToShairBuffer(&(pConn->resp), "\n"); + return tReturn; +} + +// Copies CSeq value from request, and adds standard header values in. +void propogateCSeq(struct connection *pConn) //char *pRecvBuffer, struct shairbuffer *pConn->recp.data) +{ + int tSize=0; + char *tRecPtr = getFromHeader(pConn->recv.data, "CSeq", &tSize); + addToShairBuffer(&(pConn->resp), "Audio-Jack-Status: connected; type=analog\n"); + addToShairBuffer(&(pConn->resp), "CSeq: "); + addNToShairBuffer(&(pConn->resp), tRecPtr, tSize); + addToShairBuffer(&(pConn->resp), "\n"); +} + +void cleanupBuffers(struct connection *pConn) +{ + if(pConn->recv.data != NULL) + { + free(pConn->recv.data); + pConn->recv.data = NULL; + } + if(pConn->resp.data != NULL) + { + free(pConn->resp.data); + pConn->resp.data = NULL; + } +} + +void cleanup(struct connection *pConn) +{ + cleanupBuffers(pConn); + if(pConn->hairtunes != NULL) + { + + closePipe(&(pConn->hairtunes->in[0])); + closePipe(&(pConn->hairtunes->in[1])); + closePipe(&(pConn->hairtunes->out[0])); + closePipe(&(pConn->hairtunes->out[1])); + } + if(pConn->keys != NULL) + { + if(pConn->keys->aesiv != NULL) + { + free(pConn->keys->aesiv); + } + if(pConn->keys->aeskey != NULL) + { + free(pConn->keys->aeskey); + } + if(pConn->keys->fmt != NULL) + { + free(pConn->keys->fmt); + } + pConn->keys = NULL; + } + if(pConn->clientSocket != -1) + { + close(pConn->clientSocket); + pConn->clientSocket = -1; + } +} + +int startAvahi(const char *pHWStr, const char *pServerName, int pPort) +{ + int tMaxServerName = 25; // Something reasonable? iPad showed 21, iphone 25 + int tPid = fork(); + if(tPid == 0) + { + char tName[100 + HWID_SIZE + 3]; + if(strlen(pServerName) > tMaxServerName) + { + slog(LOG_INFO,"Hey dog, we see you like long server names, " + "so we put a strncat in our command so we don't buffer overflow, while you listen to your flow.\n" + "We just used the first %d characters. Pick something shorter if you want\n", tMaxServerName); + } + + tName[0] = '\0'; + char tPort[SERVLEN]; + sprintf(tPort, "%d", pPort); + strcat(tName, pHWStr); + strcat(tName, "@"); + strncat(tName, pServerName, tMaxServerName); + slog(AVAHI_LOG_LEVEL, "Avahi/DNS-SD Name: %s\n", tName); + + execlp("avahi-publish-service", "avahi-publish-service", tName, + "_raop._tcp", tPort, "tp=UDP","sm=false","sv=false","ek=1","et=0,1", + "cn=0,1","ch=2","ss=16","sr=44100","pw=false","vn=3","txtvers=1", NULL); + execlp("dns-sd", "dns-sd", "-R", tName, + "_raop._tcp", ".", tPort, "tp=UDP","sm=false","sv=false","ek=1","et=0,1", + "cn=0,1","ch=2","ss=16","sr=44100","pw=false","vn=3","txtvers=1", NULL); + if(errno == -1) { + perror("error"); + } + + slog(LOG_INFO, "Bad error... couldn't find or failed to run: avahi-publish-service OR dns-sd\n"); + exit(1); + } + else + { + slog(LOG_DEBUG_VV, "Avahi/DNS-SD started on PID: %d\n", tPid); + } + return tPid; +} + +void printBufferInfo(struct shairbuffer *pBuf, int pLevel) +{ + slog(pLevel, "Buffer: [%s] size: %d maxchars:%d\n", pBuf->data, pBuf->current, pBuf->maxsize/sizeof(char)); +} + +int getAvailChars(struct shairbuffer *pBuf) +{ + return (pBuf->maxsize / sizeof(char)) - pBuf->current; +} + +void addToShairBuffer(struct shairbuffer *pBuf, char *pNewBuf) +{ + addNToShairBuffer(pBuf, pNewBuf, strlen(pNewBuf)); +} + +void addNToShairBuffer(struct shairbuffer *pBuf, char *pNewBuf, int pNofNewBuf) +{ + int tAvailChars = getAvailChars(pBuf); + if(pNofNewBuf > tAvailChars) + { + int tNewSize = pBuf->maxsize * 2 + MAX_SIZE + sizeof(char); + char *tTmpBuf = malloc(tNewSize); + + tTmpBuf[0] = '\0'; + memset(tTmpBuf, 0, tNewSize/sizeof(char)); + memcpy(tTmpBuf, pBuf->data, pBuf->current); + free(pBuf->data); + + pBuf->maxsize = tNewSize; + pBuf->data = tTmpBuf; + } + memcpy(pBuf->data + pBuf->current, pNewBuf, pNofNewBuf); + pBuf->current += pNofNewBuf; + if(getAvailChars(pBuf) > 1) + { + pBuf->data[pBuf->current] = '\0'; + } +} + +char *getTrimmedMalloc(char *pChar, int pSize, int pEndStr, int pAddNL) +{ + char *tTrimDest = malloc(sizeof(char) * (pSize + pEndStr + pAddNL)); + return getTrimmed(pChar, pSize, pEndStr, pAddNL, tTrimDest); +} + +// Must free returned ptr +char *getTrimmed(char *pChar, int pSize, int pEndStr, int pAddNL, char *pTrimDest) +{ + int tSize = pSize; + if(pEndStr) + { + tSize++; + } + if(pAddNL) + { + tSize++; + } + + memset(pTrimDest, 0, tSize); + memcpy(pTrimDest, pChar, pSize); + if(pAddNL) + { + pTrimDest[pSize] = '\n'; + } + if(pEndStr) + { + pTrimDest[tSize-1] = '\0'; + } + return pTrimDest; +} + +void slog(int pLevel, char *pFormat, ...) +{ + #ifdef SHAIRPORT_LOG + if(isLogEnabledFor(pLevel)) + { + va_list argp; + va_start(argp, pFormat); + vprintf(pFormat, argp); + va_end(argp); + } + #endif +} + +int isLogEnabledFor(int pLevel) +{ + if(pLevel <= kCurrentLogLevel) + { + return TRUE; + } + return FALSE; +} + +void initConnection(struct connection *pConn, struct keyring *pKeys, + struct comms *pComms, int pSocket, char *pPassword) +{ + pConn->hairtunes = pComms; + if(pKeys != NULL) + { + pConn->keys = pKeys; + pConn->keys->aesiv = NULL; + pConn->keys->aeskey = NULL; + pConn->keys->fmt = NULL; + } + pConn->recv.data = NULL; // Pre-init buffer expected to be NULL + pConn->resp.data = NULL; // Pre-init buffer expected to be NULL + pConn->clientSocket = pSocket; + if(strlen(pPassword) >0) + { + pConn->password = pPassword; + } + else + { + pConn->password = NULL; + } +} + +void closePipe(int *pPipe) +{ + if(*pPipe != -1) + { + close(*pPipe); + *pPipe = -1; + } +} + +void initBuffer(struct shairbuffer *pBuf, int pNumChars) +{ + if(pBuf->data != NULL) + { + slog(LOG_DEBUG_VV, "Hrm, buffer wasn't cleaned up....trying to free\n"); + free(pBuf->data); + slog(LOG_DEBUG_VV, "Free didn't seem to seg fault....huzzah\n"); + } + pBuf->current = 0; + pBuf->marker = 0; + pBuf->maxsize = sizeof(char) * pNumChars; + pBuf->data = malloc(pBuf->maxsize); + memset(pBuf->data, 0, pBuf->maxsize); +} + +void setKeys(struct keyring *pKeys, char *pIV, char* pAESKey, char *pFmtp) +{ + if(pKeys->aesiv != NULL) + { + free(pKeys->aesiv); + } + if(pKeys->aeskey != NULL) + { + free(pKeys->aeskey); + } + if(pKeys->fmt != NULL) + { + free(pKeys->fmt); + } + pKeys->aesiv = pIV; + pKeys->aeskey = pAESKey; + pKeys->fmt = pFmtp; +} + +RSA *loadKey() +{ +/* +char *pem = "----BEGIN RSA PRIVATE KEY-----\ +MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt/n\ +wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U/n\ +wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf/n\ +/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni//n\ +UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW/n\ +BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa/n\ +LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5/n\ +NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm/n\ +lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz/n\ +aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu/n\ +a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM/n\ +oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z/n\ +oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+/n\ +k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL/n\ +AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA/n\ +cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf/n\ +54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov/n\ +17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc/n\ +1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI/n\ +LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ/n\ +2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKaXTyY=/n\ +-----END RSA PRIVATE KEY-----"; +*/ + + FILE *pem = fopen("airport.pem", "r"); + RSA *rsa = PEM_read_RSAPrivateKey(pem, NULL, NULL, NULL); + fclose(pem); + slog(RSA_LOG_LEVEL, "RSA Key: %d\n", RSA_check_key(rsa)); + + return rsa; +} diff --git a/shairport.h b/shairport.h new file mode 100644 index 00000000..f22320ee --- /dev/null +++ b/shairport.h @@ -0,0 +1,79 @@ +#include "stdio.h" +#include "string.h" +#include "unistd.h" +#include "openssl/bio.h" +#include "openssl/ssl.h" +#include "openssl/err.h" +#include "socketlib.h" +#include "regex.h" +#include +#include + +#define DONT_USE_HAIRTUNES_MAIN +#define HWID_SIZE 6 +#define SHAIRPORT_LOG 1 +#define LOG_INFO 1 +#define LOG_DEBUG 5 +#define LOG_DEBUG_V 6 +#define LOG_DEBUG_VV 7 + +struct shairbuffer +{ + char *data; + int current; + int maxsize; + int marker; +}; + +struct keyring +{ + char *aeskey; + char *aesiv; + char *fmt; +}; + +struct comms +{ + int in[2]; + int out[2]; +}; + +struct connection +{ + struct shairbuffer recv; + struct shairbuffer resp; + struct keyring *keys; // Does not point to malloc'd memory. + struct comms *hairtunes; + int clientSocket; + char *password; +}; + +RSA *loadKey(); +int startAvahi(const char *pHwAddr, const char *pServerName, int pPort); +void cleanupBuffers(struct connection *pConnection); +void cleanup(struct connection *pConnection); +void handleClient(int pSock, char *pPassword, char *pHWADDR); +int getAvailChars(struct shairbuffer *pBuf); + +char *getTrimmedMalloc(char *pChar, int pSize, int pEndStr, int pAddNL); +char *getTrimmed(char *pChar, int pSize, int pEndStr, int pAddNL, char *pTrimDest); +void initBuffer(struct shairbuffer *pBuf, int pNumChars); +void printBufferInfo(struct shairbuffer *pBuf, int pLevel); +void addToShairBuffer(struct shairbuffer *pBuf, char *pNewBuf); +void addNToShairBuffer(struct shairbuffer *pBuf, char *pNewBuf, int pNofNewBuf); + +int readDataFromClient(int pSock, struct shairbuffer *pClientBuffer); +int parseMessage(struct connection *pConn, char *pIpStr, char *pHWADDR); + +void closePipe(int *pPipe); +void setKeys(struct keyring *pKeys, char *pIV, char* pAESKey, char *pFmtp); +void initConnection(struct connection *pConn, struct keyring *pKeys, + struct comms *pComms, int pSocket, char *pPassword); + +void writeDataToClient(int pSock, struct shairbuffer *pResponse); +void propogateCSeq(struct connection *pConn); +void convertIpToBinary(char *pIpAddress, int pIsIPv6, char *pBinaryOutput); +int buildAppleResponse(struct connection *pConn, char *pIpStr, char *pHwAddr); +void sim(int pLevel, char *pValue1, char *pValue2); +void slog(int pLevel, char *pFormat, ...); +int isLogEnabledFor(int pLevel); diff --git a/socketlib.c b/socketlib.c new file mode 100644 index 00000000..73e7790f --- /dev/null +++ b/socketlib.c @@ -0,0 +1,233 @@ +/* + * Socket Library - Common socket functions, based on stevens networking book. + * Copyright (c) M. Andrew Webster 2011 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include +#include "socketlib.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +int common_setup(struct addrinfo *pAddrInfo) +{ + int tSock; + //printAddrs(pAddrInfo); + tSock = socket(pAddrInfo->ai_family, pAddrInfo->ai_socktype, 0); + + return tSock; +} + +int setup_client(struct addrinfo *server_host) +{ + int tSockDesc = -1; + int tIdx = 0; + + while(tIdx++ < RETRY_COUNT) + { + tSockDesc = common_setup(server_host); + if (tSockDesc < 0 && tIdx >= RETRY_COUNT) + { + perror("Error: Could not create socket"); + return ERROR; + } + + if (connect(tSockDesc, server_host->ai_addr, server_host->ai_addrlen) >= 0) + { + return tSockDesc; + } + else + { + close(tSockDesc); + perror("Error: Could not connect to server"); + struct timeval tRes; + delay(RETRY_DELAY, &tRes); + } + } + printf("%d Retry attempts exceeded\n", RETRY_COUNT); + return ERROR; +} + +int getAddr(char *pHostname, char *pService, int pFamily, int pSockType, struct addrinfo **pAddrInfo) +{ + struct addrinfo hints; + int tError = 0; + memset(&hints, 0 , sizeof(hints)); + + hints.ai_family = pFamily; + hints.ai_socktype = pSockType; + if(pHostname == NULL) + { + hints.ai_flags = AI_PASSIVE; + } + + tError = getaddrinfo(pHostname, pService, &hints, pAddrInfo); + if(tError != 0) + { + printf("Error getting address info\n"); + } + return tError; +} + +int setup_server(struct addrinfo *server_addr) +{ + int tSock = common_setup(server_addr); + if (tSock < 0) + { + perror("Error: Could not create server socket"); + return ERROR; + } + + int tEnable = 1; + setsockopt(tSock, SOL_SOCKET, SO_REUSEADDR, &tEnable, sizeof (tEnable)); + if (bind(tSock, server_addr->ai_addr, server_addr->ai_addrlen) < 0) + { + close(tSock); + perror("Error: Could not bind socket"); + return ERROR; + } + + if (listen(tSock, 5) < 0) + { + close(tSock); + perror("Error: Unable to listen on server socket"); + return ERROR; + } + + return tSock; +} + + +int acceptClient(int pSock, struct addrinfo *server_addr) +{ + int tAccept = accept(pSock, server_addr->ai_addr, &server_addr->ai_addrlen); + // close the listen socket. Not expecting any more clients. + if (tAccept < 0) + { + perror("Error: Unable to accept connection to server socket"); + return ERROR; + } + else + { + //printf("..Accepted on socket: %d\n", tAccept); + } + return tAccept; +} + +int setupListenServer(struct addrinfo **pAddrInfo, int pPort) +{ + char tService[SERVLEN]; + sprintf(tService, "%d", pPort); // copies port to string + int tFamily = AF_INET; + #ifdef AF_INET6 + //printf("Listening on IPv6 Socket\n"); + tFamily = AF_INET6; + #else + //printf("Listening on IPv4 Socket"); + #endif + if(getAddr(NULL, tService, tFamily, SOCK_STREAM, pAddrInfo)) + { + return ERROR; // getAddr prints out error message + } + + int tSocketDescriptor = setup_server(*pAddrInfo); + char tAddr[INET6_ADDRSTRLEN]; + socklen_t tSize = INET6_ADDRSTRLEN; + inet_ntop((*pAddrInfo)->ai_family, (*pAddrInfo)->ai_addr, tAddr, tSize); + //printf("Size is: %d\n", tSize); + return tSocketDescriptor; +} + +void delay(long pMillisecs, struct timeval *pRes) +{ + pRes->tv_sec = pMillisecs / 1000; + pRes->tv_usec = (pMillisecs - (pRes->tv_sec * 1000)) * 1000; + select(0,NULL,NULL,NULL,pRes); +} + +// From http://www.ioncannon.net/programming/34/howto-base64-encode-with-cc-and-openssl/ + +//int main(int argc, char **argv) +//{ +// char *output = decode_base64("WU9ZTyEA\n\0", strlen("WU9ZTyEA\n\0")); +// printf("Unbase64: *%s*\n", output); +// free(output);/ +//} + +char *decode_base64(unsigned char *input, int length, int *pActualLength) +{ + // Needs All no_no flags for proper RSA AES KEY Descrypt + BIO *b64, *bmem; + + char *buffer = (char *)malloc(length); + memset(buffer, 0, length); + + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + + bmem = BIO_new_mem_buf(input, length); + BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_push(b64, bmem); + + BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL); + + *pActualLength = BIO_read(bmem, buffer, length); + + BIO_free_all(bmem); + + return buffer; +} + +char *encode_base64(unsigned char *input, int length) +{ + BIO *bmem, *b64; + BUF_MEM *bptr; + + b64 = BIO_new(BIO_f_base64()); + // This enables/disables nls + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + + b64 = BIO_push(b64, bmem); + + BIO_write(b64, input, length); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); + + char *buff = (char *)malloc(bptr->length); + memcpy(buff, bptr->data, bptr->length-1); + buff[bptr->length-1] = 0; + + BIO_free_all(b64); + + return buff; +} \ No newline at end of file diff --git a/socketlib.h b/socketlib.h new file mode 100644 index 00000000..21241534 --- /dev/null +++ b/socketlib.h @@ -0,0 +1,38 @@ +/* Name: M. Andrew Webster + * Based on stevens networking book + * Description: function prototypes for socket library + */ + +#ifndef _SOCKETLIB_H +#define _SOCKETLIB_H + +#include +#include +#include +#include + + +#define MAX_SIZE 8192 +#define PORT 5000 +#define RETRY_COUNT 7 +#define SERVLEN 80 +#define RETRY_DELAY 1000 + +#define ERROR -1 +#define DEFAULT_IP "127.0.0.1" +#define DEFAULT_UNIX "/unix" + +int setup_client(struct addrinfo *server_info); +int setup_server(struct addrinfo *server_address); +int setupListenServer(struct addrinfo **pAddrInfo, int pPort); +int acceptClient(int pSock, struct addrinfo *server_addr); +void delay(long pMillisecs, struct timeval *pRes); +int getAddr(char *pHostname, char *pService, int pFamily, int pSockType, struct addrinfo **pAddrInfo); + +// All calls to decode and encode need to be freed +char *decode_base64(unsigned char *input, int length, int *tActualLength); +// All calls to decode and encode need to be freed +char *encode_base64(unsigned char *input, int length); + + +#endif