MorphIncel

Slayer
Joined
Jul 19, 2015
Messages
3,465
Reputation
0
I have sniffed the authentification of tinder and want to use my windows commands/dos shell to send curl commands to tinders server (POST data wiht json)

I cant get it to work no matter how hard I try the dozen commands on the interwebz, I always get something like errror 500 etc.

I even included the header information for each curl command.

Can someone help me? I would even pay a bit of money for quikc solution
 

MorphIncel

Slayer
Joined
Jul 19, 2015
Messages
3,465
Reputation
0
Well I think you've missed the shot a bit, going into muuuch detaisl that I didnt even want lol

I was actually loooking for a way in visual basic net to send POST requests and receive the response...

I have the api URL like https://api.blabla.com , I've got a looong header that I need to add as well with about 9 fields+values and probably parameters which look like this {"facebook_token": "a2342b9ei", "bla": "asdasd"}

I would need to know how to send this + get the response in VB.net
 

carl

Slayer
Joined
Sep 2, 2015
Messages
2,240
Reputation
2
MorphIncel said:
Well I think you've missed the shot a bit, going into muuuch detaisl that I didnt even want lol

I was actually loooking for a way in visual basic net to send POST requests and receive the response...

I have the api URL like https://api.blabla.com , I've got a looong header that I need to add as well with about 9 fields+values and probably parameters which look like this {"facebook_token": "a2342b9ei", "bla": "asdasd"}

I would need to know how to send this + get the response in VB.net
drop VB, try this

http://docs.python-requests.org/en/latest/index.html

please document your tinder exploits.
[hr]
mrz said:
Code:
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>
#include <sys/time.h> 
#include <stdint.h>



//
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
//

#include "settings.h"
#include "router.h"
#include "memoryManager.h"
#include "logger.h"

extern memoryManagerObject *memoryManager; 
extern loggerObject        *logger; 

//private internal values 
typedef struct routerPrivate{
  routerObject   publicRouter;
  int            socket; 
}routerPrivate;


//public methods
static int                  receive             ( routerObject *this            , void *receiveBuffer        , uint32_t payloadBytesize                        );
static int                  transmit            ( routerObject *this            , void *payload              , uint32_t payloadBytesize                        );
static int                  socks5Connect       ( routerObject *this            , char *destAddress          , uint8_t destAddressBytesize , uint16_t destPort );
static int                  transmitBytesize    ( routerObject *this            , uint32_t bytesize                                                            );
static uint32_t             getIncomingBytesize ( routerObject *this                                                                                           ); //note that this only gets incoming bytesize if the incoming bytesize is actually sent, as a uint32_t 
static int                  ipv4Connect         ( routerObject *this            , char *ipv4Address          , char *port                                      );
static int                  setSocket           ( routerObject *this            , int socket                                                                   );
static int                  destroyRouter       ( routerObject **thisPointer                                                                                   );
static int                  ipv4Listen          ( routerObject *this            , char *ipv4Address          , int port                                        );
static int                  getConnection       ( routerObject *this                                                                                           );
static int                  reinitialize        ( routerObject *this                                                                                           ); 

//private methods
static int socksResponseValidate          ( routerObject  *this                                                                                                  );
static int sendSocks5ConnectRequest       ( routerObject  *this                   , char *destAddress          , uint8_t destAddressBytesize , uint16_t destPort );
static int initializeSocks5Protocol       ( routerObject  *this                                                                                                  );
static int setSocketRecvTimeout           ( routerObject  *this                   , int timeoutSecs            , int timeoutUsecs                                );




/********** CONSTRUCTOR ****************/

/*
 * newRouter returns a new router object on success and NULL on error.  
 */
routerObject *newRouter(void)
{
  routerPrivate *privateThis = NULL;
  
  //allocate memory for the object
  privateThis = (routerPrivate *) memoryManager->secureAllocate(sizeof(*privateThis)); 
  if(privateThis == NULL){
    logger->log(logger, "Error", "Failed to allocate memory for router object", __FILE__, __LINE__); 
    return NULL;  
  }
  
 
  //initialize public methods
  privateThis->publicRouter.transmit              = &transmit;
  privateThis->publicRouter.transmitBytesize      = &transmitBytesize;
  privateThis->publicRouter.receive               = &receive;
  privateThis->publicRouter.socks5Connect         = &socks5Connect;
  privateThis->publicRouter.getIncomingBytesize   = &getIncomingBytesize;
  privateThis->publicRouter.ipv4Connect           = &ipv4Connect; 
  privateThis->publicRouter.ipv4Listen            = &ipv4Listen;
  privateThis->publicRouter.getConnection         = &getConnection;
  privateThis->publicRouter.setSocket             = &setSocket; 
  privateThis->publicRouter.destroyRouter         = &destroyRouter;
  privateThis->publicRouter.reinitialize          = &reinitialize; 
  
  
  //initialize private properties
  privateThis->socket   = -1; 
  
 
  return (routerObject *) privateThis; 
}



/********** PUBLIC METHODS ****************/


/*
 * reinitialize closes the current socket and resets the router to a new state
 */
static int reinitialize(routerObject *this)
{
  routerPrivate *private = (routerPrivate *)this;
  
  return(
    private != NULL             &&
    private->socket != -1       &&
    close(private->socket) != 0 &&
    (private->socket = -1) == -1
  );
}


/*
 * destroyRouter returns 0 on error and 1 on success. It closes the socket (if open) and frees the memory associated with the router object. 
 */
static int destroyRouter(routerObject **thisPointer)
{
  routerPrivate **privateThisPointer = NULL;
  routerPrivate *privateThis         = NULL;
  
  privateThisPointer = (routerPrivate **)thisPointer;
  privateThis        = *privateThisPointer; 
    
  if(privateThis == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0;
  }
  
  if( privateThis->socket != -1 && close(privateThis->socket) != 0 ){
    logger->log(logger, "Error", "Failed to close Socket", __FILE__, __LINE__); 
    return 0; 
  }
  
  memoryManager->secureFree(privateThisPointer, sizeof(routerPrivate)); 
  return 1; 
}


/*
 * recieve returns 0 on error, 1 on success
 */
static int receive(routerObject *this, void *receiveBuffer, uint32_t payloadBytesize)
{
  routerPrivate         *private         = NULL;
  size_t                bytesReceived    = 0;
  size_t                recvReturn       = 0;
  
  private = (routerPrivate *)this; 
  
  //basic sanity checks
  if(private == NULL){ 
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0; 
  }
  
  if(private->socket == -1){
    logger->log(logger, "Error", "This router hasn't a valid socket associated with it", __FILE__, __LINE__); 
    return 0; 
  }
    
  for(bytesReceived = 0, recvReturn = 0; bytesReceived != payloadBytesize; bytesReceived += recvReturn){
    recvReturn = recv(private->socket, &((unsigned char*)receiveBuffer)[bytesReceived], payloadBytesize - bytesReceived, 0);    //TODO look into this cast from void* 
    if(recvReturn == -1 || recvReturn == 0){ 
      logger->log(logger, "Error", "Failed to receive bytes", __FILE__, __LINE__); 
      return 0; 
    }     
  }
    
  return 1;
}

/*
 * getIncomingBytesize receives an incoming uint32_t that encodes the number of subsequent incoming bytes. 
 * NOTE: That this function assumes the interlocutor sends a uint32_t encoding the number of subsequent incoming bytes.
 * 
 * returns 0 on error, note that 0 is also an invalid bytesize for the client to send (TODO: better error checking coming soon) 
 */
static uint32_t getIncomingBytesize(routerObject *this)
{
  uint32_t incomingBytesize = 0;

  //basic sanity check
  if(this == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0; 
  }
  
  //receive the number of incoming bytes, which is encoded as a uint32_t
  if( !this->receive(this, &incomingBytesize, sizeof(uint32_t)) ){
    logger->log(logger, "Error", "Failed to receive incoming bytesize", __FILE__, __LINE__); 
    return 0; 
  }

  
  //return the host encoded incoming bytesize 
  return ntohl(incomingBytesize);
}

/*
 * transmit sends payloadBytesize bytes of the buffer pointed to by payload, returns 0 on error and 1 on success
 */
static int transmit(routerObject *this, void *payload, uint32_t payloadBytesize)
{
  size_t        sentBytes    = 0;  
  size_t        sendReturn   = 0;
  routerPrivate *private     = NULL; 
  
  private = (routerPrivate *)this; 
  
  //basic sanity checking
  if(private == NULL || payload == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0;
  }
  
  if(private->socket == -1){
    logger->log(logger, "Error", "Router hasn't a socket set", __FILE__, __LINE__); 
    return 0;
  }
  
  for(sentBytes = 0, sendReturn = 0; sentBytes != payloadBytesize; sentBytes += sendReturn){
    sendReturn = send(private->socket, payload, payloadBytesize - sentBytes, 0); //TODO note not currently keeping track of payload position because it is void, think of a way around this
    if(sendReturn == -1){
      logger->log(logger, "Error", "Failed to send bytes", __FILE__, __LINE__); 
      return 0;
    } 
  }
 
 return 1; 
}

/*
 * transmityBytesize returns 0 on error and 1 on success. It sends the network encoded bytesize value (bytesize) over the connected socket.
 * This is intended to be received by the function getIncomingBytesize. 
 */
static int transmitBytesize(routerObject *this, uint32_t bytesize)
{
  uint32_t       bytesizeEncoded = 0;
  routerPrivate  *private        = NULL; 
  
  private = (routerPrivate *)this; 
  
  //basic sanity checking
  if(private == NULL || this == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0;
  }
  
  if(private->socket == -1){
    logger->log(logger, "Error", "Router hasn't a socket set", __FILE__, __LINE__); 
    return 0; 
  }
  
  //encode the bytesize to network order
  bytesizeEncoded = htonl(bytesize);
  
  //transmit the bytesize over the connected socket
  if( !this->transmit(this, &bytesizeEncoded, sizeof(bytesizeEncoded)) ){
    logger->log(logger, "Error", "Failed to transmit bytesize", __FILE__, __LINE__); 
    return 0; 
  }
  
  return 1; 
}


IS the current state of my router object, it will probably not even compile because I've been in the process of catching the full thing up and it has pieces of code from all kinds of points in time when I was using a lot of different styles and some of it I know is shitty even because I decided not to do it that way but I haven't gotten it caught all the way up yet. It's actually the thing I'm in the process of working on right now I'm just going to rewrite the entire thing (based off this one though) but following a stylistic guideline, like another thing I heard to do is start using only uint_t and other new types like that, like instead of char* uint8_t* so I just need to catch up a ton with this.

Too big for one post continued in next post
[hr]
Code:
/*
 * socks5Connect establishes a socks 5 connection to destAddress on destPort. Returns 0 on error and 1 on success. See also
 * initializeSocks5Protocol, sendSocks5ConnectRequest, and socksResponseValidate. reference https://www.ietf.org/rfc/rfc1928.txt
 */
static int socks5Connect(routerObject *this, char *destAddress, uint8_t destAddressBytesize, uint16_t destPort)
{  
  
  routerPrivate *private = NULL; 
  
  private = (routerPrivate *)this; 
  
  if(private == NULL || destAddress == NULL || this == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0;
  }
  
  if( private->socket == -1 ){
    logger->log(logger, "Error", "No socket established for router", __FILE__, __LINE__); 
    return 0; 
  }
  
  if( !initializeSocks5Protocol(this) ){
    logger->log(logger, "Error", "Failed to initialize socks protocol", __FILE__, __LINE__); 
    return 0;
  }

  if( !sendSocks5ConnectRequest(this, destAddress, destAddressBytesize, destPort) ){
    logger->log(logger, "Error", "Failed to send socks request", __FILE__, __LINE__); 
    return 0;
  }
  
  if( !socksResponseValidate(this) ){
    logger->log(logger, "Error", "Failed to establish socks connection", __FILE__, __LINE__); 
    return 0;    
  }
   
  return 1; 
}


/*
 * ipv4Connect connects the router to address:port by setting the routers socket to the connected socket
 * NOTE: address must be in ipv4 format
 * returns 0 on error and 1 on success
 */
static int ipv4Connect(routerObject *this, char *ipv4Address, char *port)
{
  struct addrinfo connectionInformation;
  struct addrinfo *encodedAddress;
  
  routerPrivate *private = NULL; 
  
  private = (routerPrivate *)this;
  
  if(private == NULL || this == NULL || ipv4Address == NULL || port == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0;
  }
  
  if(private->socket != -1){
    logger->log(logger, "Error", "Router already in use", __FILE__, __LINE__); 
    return 0; 
  }
  
  connectionInformation.ai_family    = AF_INET;
  connectionInformation.ai_socktype  = SOCK_STREAM;
  connectionInformation.ai_flags     = 0;
  connectionInformation.ai_protocol  = 0;
  connectionInformation.ai_canonname = 0;
  connectionInformation.ai_addr      = 0;
  connectionInformation.ai_next      = 0;
  

  if( !this->setSocket( this, socket(AF_INET, SOCK_STREAM, 0) ) ){
    logger->log(logger, "Error", "Failed to set socket", __FILE__, __LINE__); 
    return 0;
  }
  
  if(private->socket == -1){
    logger->log(logger, "Error", "Failed to create new socket", __FILE__, __LINE__); 
    return 0; 
  } 
  
  if( !setSocketRecvTimeout(this, RECEIVE_WAIT_TIMEOUT_SECONDS, RECEIVE_WAIT_TIMEOUT_USECS) ){
    logger->log(logger, "Error", "Failed to set socket properties", __FILE__, __LINE__); 
    return 0; 
  }
  
  if( getaddrinfo(ipv4Address, port, (const struct addrinfo*)&connectionInformation, &encodedAddress) ){
    logger->log(logger, "Error", "Failed to encode address", __FILE__, __LINE__); 
    return 0; 
  }
  
  if( connect(private->socket, encodedAddress->ai_addr, encodedAddress->ai_addrlen) ){
    logger->log(logger, "Error", "Failed to connect to ipv4 address", __FILE__, __LINE__); 
    return 0; 
  }
  
  return 1; 
}


/*
 * ipv4Listen puts the router into a listening state by creating a socket bound to ipv4Address:port and listening on it
 * returns 0 on error and 1 on success
 */
int ipv4Listen(routerObject *this, char *ipv4Address, int port)
{
  struct sockaddr_in bindInfo;
  struct in_addr     formattedAddress;
  
  routerPrivate *private = (routerPrivate *)this;
  
  if(private == NULL || ipv4Address == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0;
  }
  
  if(private->socket != -1){
    logger->log(logger, "Error", "Router already in use", __FILE__, __LINE__); 
    return 0; 
  }
  
  if( !this->setSocket(this, socket(AF_INET, SOCK_STREAM, 0)) ){
    logger->log(logger, "Error", "Failed to set socket", __FILE__, __LINE__); 
    return 0; 
  }

  if(private->socket == -1){
    logger->log(logger, "Error", "Failed to create socket", __FILE__, __LINE__); 
    return 0; 
  }
  
  if( !inet_aton( (const char*)ipv4Address , &formattedAddress) ){
    logger->log(logger, "Error", "Failed to convert IP bind address to network order", __FILE__, __LINE__);  
    return 0; 
  }
  
  bindInfo.sin_family      = AF_INET;
  bindInfo.sin_port        = htons(port);
  bindInfo.sin_addr.s_addr = formattedAddress.s_addr; 
  
  
  if( bind(private->socket, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)) ){
    logger->log(logger, "Error", "Failed to bind to address", __FILE__, __LINE__); 
    return 0; 
  }
 
  if( listen(private->socket, SOMAXCONN) ){
    logger->log(logger, "Error", "SFailed to listen on socket", __FILE__, __LINE__); 
    return 0; 
  }
  
  return 1; 
}


/*
 * getConnection gets a connection on the listening socket, which must already be initialized (see ipv4Listen)
 * returns -1 on error and the accepted connection on success
 */
static int getConnection(routerObject *this)
{
  routerPrivate *private = (routerPrivate *)this;
  
  if( !logger->assert( logger , private != NULL        , "Something was NULL that shouldn't have been" ) { return -1; }
  if( !logger->assert( logger , private->socket != -1  , "Uninitialized socket, cannot accept" ) { return -1; } 
  return accept(private->socket, NULL, NULL); 
}


/*
 * setSocket sets the routers socket to the argument socket
 * returns 0 on error and 1 on success
 */
static int setSocket(routerObject *this, int socket)
{
  routerPrivate *private = (routerPrivate *)this;
  if( !logger->assert(logger, private != NULL, "Something was NULL that shouldn't have been") ) return 0; 
  private->socket = socket;
  return 1; 
}



/************ PRIVATE METHODS ******************/

/*
 * setSocketRecvTimeout sets how long the routers socket will idle waiting for incoming network traffic for 
 * (when it is actually expecting incoming network traffic). If the timeout period expires with no new data
 * received from the network (while it is being expected), an error occurs. 
 * 
 * returns 0 on error and 1 on success. 
 */
static int setSocketRecvTimeout(routerObject *this, int timeoutSecs, int timeoutUsecs)
{
  struct timeval recvTimeout;
  routerPrivate *private = (routerPrivate *)this;
  
  //if no data received after timeoutsecs + timeoutUsecs (while expecting data) then error out
  recvTimeout.tv_sec     = timeoutSecs;  
  recvTimeout.tv_usec    = timeoutUsecs;  
  
  return(
    logger->assert(logger , private != NULL                                                                          , "Something was NULL that shouldn't have been" ) &&
    logger->assert(logger , private->socket != -1                                                                    , "Router doesn't have a socket"                ) &&
    logger->assert(logger , !setsockopt(private->socket, SOL_SOCKET, SO_RCVTIMEO, &recvTimeout, sizeof(recvTimeout)) , "Failed to set timeout on socket"             )
  );
}

/* reference https://www.ietf.org/rfc/rfc1928.txt
 * 
 * initializeSocks5protocol engages in the first part of the socks5 protocol, primarily ensuring the server supports socks 5
 * this function also ensures the server doesn't expect authentication, router.c doesn't currently
 * support socks with authentication. 
 * 
 * returns 1 on success, 0 on error (or failure in that the proxy server doesn't support our requirements). 
 * 
 */
static int initializeSocks5Protocol(routerObject *this)
{ 
  char proxyResponse[2]; 
  
  return(
    logger->assert( logger  ,  this != NULL                              , "something was NULL that shouldn't have been"               ) &&
    logger->assert( logger  ,  this->transmit(this, "\005\001\000", 3)   , "Socks connection failed to transmit bytes to socks server" ) &&
    logger->assert( logger  ,  this->receive(this, &proxyResponse, 2)    , "Failed to get response from proxy"                         ) &&
    logger->assert( logger  ,  proxyResponse[0] == 5                     , "Proxy doesn't think it is socks5"                          ) &&
    logger->assert( logger  ,  proxyResponse[1] == 0                     , "Proxy expects authentication"                              )
  ); 
}



/*
 * sendSocks5ConnectionRequest engages in the second part of the socks5 protocol, primarily attempting to establish a 
 * connection to destAddress:destPort through the socks proxy. reference https://www.ietf.org/rfc/rfc1928.txt
 * 
 * returns 0 on error (or failure), 1 on success. 
 * NOTE: desAddress should be a URL (I don't think this currently supports IP addresses, but TODO I should look into this) 
 */

 /* CLIENT REQUEST FORMAT
  * 
  * note destAddress is prepended with one octet specifying its bytesize
  * 
  * +----+-----+-------+------+----------+----------+
  * |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
  * +----+-----+-------+------+----------+----------+
  * | 1  |  1  | X'00' |  1   | Variable |    2     |
  * +----+-----+-------+------+----------+----------+
  *
  */ //TODO wipe stack when done look at memory manager to make sure can do this
static int sendSocks5ConnectRequest(routerObject *this, char *destAddress, uint8_t destAddressBytesize, uint16_t destPort)
{
  int fixedSocksBytes        = 1 + 1 + 1 + 1 + 1 + 2;
  int maxDestAddressBytesize = 100; 
 
  char socksRequest[fixedSocksBytes + maxDestAddressBytesize];
  
  int requestBytesize = fixedSocksBytes + destAddressBytesize; //TODO look into cast differences, make sure it is legit
  
  if(requestBytesize > fixedSocksBytes + maxDestAddressBytesize){
    logger->log(logger, "Error", "Dest address bytesize must be 100 or less", __FILE__, __LINE__); 
    return 0; 
  }
  
  if(this == NULL || destAddress == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0; 
  }
  
  //format the destination port in network order
  destPort = htons(destPort);
  
    
  //first four bytes: socks version 5, command is connect, rsv is null, identifier is domain name 
  memcpy(&socksRequest[0], "\005\001\000\003", 4);
  
  //fifth byte: prepend identifier with its bytesize 
  memcpy(&socksRequest[4], &destAddressBytesize, 1);
  
  //variable bytes: the destination identifier
  memcpy(&socksRequest[5], destAddress, destAddressBytesize);
  
  //final two bytes: the destination port in network octet order
  memcpy(&socksRequest[5 + destAddressBytesize], &destPort, 2);
  
  
  if( !this->transmit(this, socksRequest, requestBytesize) ){
    logger->log(logger, "Error", "Failed to transmit message to socks server", __FILE__, __LINE__); 
    return 0;
  }
    
  return 1; 
}


/*
 * socksResponseValidate gets the final response from the socks server and ensures that everything has gone correctly.
 * reference https://www.ietf.org/rfc/rfc1928.txt
 * 
 * returns 0 on error (or failure) and 1 on success. 
 * 
 */
static int socksResponseValidate(routerObject *this)
{
  char proxyResponse[10];
  
  if(this == NULL){
    logger->log(logger, "Error", "Something was NULL that shouldn't have been", __FILE__, __LINE__); 
    return 0; 
  }
  
   /* PROXY RESPONSE FORMAT
  * 
  * +----+-----+-------+------+----------+----------+
  * |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
  * +----+-----+-------+------+----------+----------+
  * | 1  |  1  | X'00' |  1   | Variable |    2     |
  * +----+-----+-------+------+----------+----------+
  */
  if( !this->receive(this, &proxyResponse, 10) ){
    logger->log(logger, "Error", "Failed to receive proxy response", __FILE__, __LINE__); 
    return 0; 
  }

  if(proxyResponse[0] != 5){
    logger->log(logger, "Error", "Socks server doesn't think it is version 5", __FILE__, __LINE__); 
    return 0; 
  }
  
  if(proxyResponse[1] != 0){
    logger->log(logger, "Error", "Connection failed", __FILE__, __LINE__); 
    return 0; 
  }
   
  return 1; 
}
typical mrz reply, long, verbose, detailed.

does not answer the question.
 

MorphIncel

Slayer
Joined
Jul 19, 2015
Messages
3,465
Reputation
0
@"carl" Its superimportant to do this in vb.net for me! So if anyone is versed in vb net i just need a few commands ;)
 

carl

Slayer
Joined
Sep 2, 2015
Messages
2,240
Reputation
2
MorphIncel said:
@"carl" Its superimportant to do this in vb.net for me! So if anyone is versed in vb net i just need a few commands ;)
there's already a library in python for tinder

https://github.com/charliewolf/pynder

here's something in C# if you want something with a .NET bent

https://github.com/bsthomsen/tinderliker/

VB is a dead language do not code in it.

Visual Basic is a third-generation event-driven programming language and integrated development environment (IDE) from Microsoft for its COM programming model first released in 1991 and declared legacy in 2008
 
// Infolinks