mIRC Scripting

mIRC: Sockets

This article assumes that you have intermediate to advanced knowledge of the mIRC Scripting Language.

Contents

Sockets

A socket, in networking terms, is simply a combination of an IP address and a port number. For our purposes we can go with a one end-point of a two-way communication link between two processes running on the network.

An IP address is the numerical identification that is assigned to devices that utilize the Internet Protocol. A port number is a process-specific identifier that serves an end point.

For example, look at the following socket: 74.125.91.104:80. The first part is the IP address (in this example we used www.google.com). The number that follows the colon (:) is the port number. In this case it's port number 80, which happens to be the default HTTP port.

Ports:

A port number is a 16-bit unsigned integer, thus ranging from 0 to 65535. (216-1), giving you a total of 65,536 ports. Ports 1-1023 are called well known ports, ports 1024-49151 are registered ports, and ports in the rage of 49152–65535 are dynamic and/or private they cannot be registered.

Be careful not to be confused between the client and server ports. The server port is the port in which the client (you) will attempt to establish a connection; the server listens to incoming connection on this port. The client port is the port at the client’s end, which are an incremental number and a much higher port number.

Here are a few commonly used ports:

FTP data/command 20/21
TELNET 23
HTTP 80
SMTP 25
POP3 (email) 110
Authentication Service (like IRC Idents) 113
HTTP over TLS/SSL (HTTPS) 443
IRC 6665-6669

 

mIRC Sockets:

mIRC gives you the ability to create a raw socket connections with an end point. This gives you the ability to do anything from getting information from a website (like the weather or games stats) to uploading or downloading files and posting on a forum.

mIRC lets you create two types of sockets: TCP and UDP. Both are core members of the Internet Protocol Suite (TCP/IP).

Here are a few basic points regarding TCP and UDP:

Transmission Control Protocol (TCP):
     - Connection-Oriented Protocols (Virtual Circuit Approach)
     - Reliable - guarantees data will be delivered in the same order as transmitted
     - Used when reliable transport required and speed is secondary

User Datagram Protocol (UDP):
     - Connectionless Service
     - No logical connection established between End Systems
     - Unreliable - "best effort" delivery
     - Used when speed is required and guaranteed delivery is secondary

mIRC TCP Sockets:

TCP is by large the major core member of the Internet Protocol Suite.

Creating A Connection

Since we are using the TCP protocol, we must first establish a connection with the server before any data can be transmitted.

The basic syntax for opening a TCP socket is:

/sockopen -de [bindip] <name> <address> <port>
  ;-d tells mIRC that the ip you specified is a bind address
  ; -e creates an SSL connection. (we will talk about this later)

  ;For simplicity sake we will go with the more basic syntax:

/sockopen <name> <address> <port>

For the purpose of this tutorial, we will create 3 different sockets to the following locations: Our own demo page, Wikipedia, and YouTube.

In example 3 I introduced another command. The /sockMark command. I suggest you take a note to its existence.

Example 1: Demo Page Examples 2: Wikipedia Examples 3: YouTube

Demo Page

Since we want to socket to our silly demo page, http://www.zigwap.com/mirc/socket-demo.php, our sockopen command will look something like this:

Alias Demo {
  sockOpen demo www.zigwap.com 80
}

Creating a socket connection by the name "demo"

As a precaution, in order to not attempt to open an already opened socket, we will close it. If the socket is not open, mIRC will do nothing. Later on in the article I will explain how to dynamically name sockets which will give you the ability create as many sockets as you want.

Alias Demo {
  sockClose demo
  sockOpen demo www.zigwap.com 80
}

 

 

Sending Request

Once connection has been made the on SockOpen event will get triggered. This is where all our requests for pages or files go. The information we send is known as the request header.

There are two ways of sending data to the server: POST and GET. When requesting a normal page, you will most likely be using the GET method, when submitting a form, it depends. When dealing with forms, by simply looking at the source code you can tell if it’s a POST or a GET method:

<form id="FooBar" method="post" action="">

The most basic GET request will follow this basic syntax:

GET /folder/file.html HTTP/1.1
Host: www.example.com
<BLANK LINE>

Let’s take a look at the header a little closer:

GET /folder/file.html HTTP/1.1

This line is made up of three parts: method, path and version. The "GET", which SHOULD be always in uppercase letters, is the method. We will talk about the POST method later on. The next part is the path, relative to the root folder of the website. If our website is www.example.com/pub/foo/bar.html, our path would be /pub/foo/bar.html. Lastly, the final part of this line is the HTTP version, for all practical reasons, you will probably using version 1.1.

Next is the Host header

Host: www.example.com

The Host header is required in HTTP version 1.1. Please note: it should be "Host:", not "host:" or "HOST:". If you forget to include this line, the server will send you an error 400 (Bad Request) status code.

The basic on sockopen event is as followed:

on *:SockOpen:sockname: {
  ;Your header goes here
}

To send data to the server we use the /sockWrite command. The sockwrite command has the following syntax:

/sockwrite [-tnb] <name> [numbytes] <text|%var|&binvar>
  ;-t switch force mIRC to treat text that looks like
    &binary variables as plain text

  ;-n switch appends $crlf to the end if it’s not a binary
    string or if it doesn’t have a $crlf at the end.

Let’s continue with our examples:

Example 1: Demo Page Examples 2: Wikipedia Examples 3: YouTube

Demo Page

Remember that the page we want to socket to is http://www.zigwap.com/mirc/socket-demo.php. Our sockopen event will look something like this: (In this example I will be using version 1.0)

on *:SockOpen:demo: {
  sockwrite -nt demo GET /mirc/socket-demo.php HTTP/1.0
  sockwrite -nt demo Host: www.zigwap.com
  sockwrite -nt demo $crlf
}

Until this point, our combined script should look something like this:

Alias Demo {
  sockClose demo
  sockOpen demo www.zigwap.com 80
}

on *:SockOpen:demo: {
  sockwrite -nt demo GET /mirc/socket-demo.php HTTP/1.0
  sockwrite -nt demo Host: www.zigwap.com
  sockwrite -nt demo $crlf
}

 

 

Coding style:

I would like to point out a few more things before we continue. Coding style is a personal thing and different people code things differently. For example:

on *:SockOpen:YouTube: {
  sockwrite -nt YouTube GET /watch?v= $+ $sock($sockName).mark HTTP/1.1
  sockwrite -nt YouTube Host: www.youtube.com
  sockwrite -nt YouTube $crlf
}

A lot of scripters would prefer to write it like this:

on *:SockOpen:YouTube: {
  var %<< sockwrite -nt YouTube
  %<< GET /watch?v= $+ $sock($sockName).mark HTTP/1.1
  %<< Host: www.youtube.com
  %<< $crlf
}

It’s up to you to decide which one you like most.

URL Encoding

Specified by the RFC 1738:

  Many URL schemes reserve certain characters for a special meaning: their appearance in the scheme-specific part of the URL has a designated semantics. If the character corresponding to an octet is reserved in a scheme, the octet must be encoded. The characters ";", "/", "?", ":", "@", "=" and "&" are the characters which may be reserved for special meaning within a scheme. No other characters may be reserved within a scheme.
  Usually a URL has the same interpretation when an octet is represented by a character and when it encoded. However, this is not true for reserved characters: encoding a character reserved for a particular scheme may change the semantics of a URL.
  Thus, only alphanumerics, the special characters "$-_.+!*'(),", and reserved characters used for their reserved purposes may be used unencoded within a URL.
  On the other hand, characters that are not required to be encoded (including alphanumerics) may be encoded within the scheme-specific part of a URL, as long as they are not being used for a reserved purpose.

We can go with this alias:

alias urlEncode return $regsubex($$1,/([\W\s])/Sg,$+(%,$base($asc(\t),10,16,2)))

To decode:

alias urlDeEncode return $regsubex($replace($1,+,$chr(32)),/%([A-F\d]{2})/gi,$chr($base(\1,16,10)))

When sending data in the header, ALL the value MUST be encoded. Their names should not. For example, if the URL is www.example.com/foo/bar.php?foo=<VALUE>

on *:SockOpen:example: {
  sockwrite -nt example GET /foo/bar.php?foo= $+ $urlEncode(<VALUE>) HTTP/1.1
  sockwrite -nt example Host: www.example.com
  sockwrite -nt example $crlf
}

 

Reading Incoming Data

Once the server receives your request, it will send the response back to you. This will trigger the on SockRead event. The basic syntax of the on sockread event is:

on *:SockRead:sockname: {
  ;Your code goes here
}

It is important to remember that each time the server replies with a line, the sockread event will trigger.

For debugging purposes I will echo all the information to a window (@ $+ $sockName). This makes it easier to find the section of html we want to retrieve and parse.

On 1:SockRead:Example1: {
  window -de @ $+ $sockName -1 -1 500 500
  var %read
  sockread %read
  aline -p @ $+ $sockName : $+ %read
}

Let’s go back to our examples: (I would point out the User-Agent header in examples 2, Wikipedia.)

Example 1: Demo Page Examples 2: Wikipedia Examples 3: YouTube

Demo Page

When I printed out the entire source the server sent us. The first part is the header, follows by a blank space, and follows by the actual page data. It should look something like this:

:HTTP/1.1 200 OK
:Date: Thu, 16 Apr 2009 04:47:40 GMT
:Server: Apache
:X-Powered-By: PHP/4.4.9
:Connection: close
:Content-Type: text/html
:
:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
:<html xmlns="http://www.w3.org/1999/xhtml">
:<head>
:<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
:<meta name="robots" content="noindex,follow" />
:<title>ZigWap - Demo Page</title>
:</head>
: <body>
: <div align="center">
: <p>This is an example page!</p>
: <p>This webpage is dedicated for the socket tutorial purpose. </p>
: </div>
: <p align="center">Your random color is: Blue</p>
: </body>
:</html>

In this example we will be trying to retrieve the random color line. A simple if statement to check for *Your random colors is* should be sufficient enough.

Alias Demo {
  sockClose demo
  sockOpen demo www.zigwap.com 80
}

on *:SockOpen:demo: {
  sockwrite -nt demo GET /mirc/socket-demo.php HTTP/1.0
  sockwrite -nt demo Host: www.zigwap.com
  sockwrite -nt demo $crlf
}

on *:SockRead:demo: {
  var %read
  sockRead %read
  if (*Your random color is: * iswm %read) {
    tokenize 32 %read
    echo $color(info) -ae Random Color: $remove($6-,</p>)
  }
}

When we located the line we wanted, I tokenized it by spaces (char 32). The 6th token will be COLOR</p>. At this point I simply removed the "</p>" part.

 

$noHTML Alias:

The noHTML alias is a very handy alias that strips html tags from a string:

alias NoHTML return $regsubex($1,/<[^>]+(?:>|$)|^[^<>]+>/g,)

For example:

//echo -a $noHTML(<strong>Example</strong> - <p>This is an <em>example</em></p>)

Will output:

Example - This is an example

Error Handling

Errors can occur. It is your responsibility to check for them. The $sockerr identifier must be checked every on sockread event and after every /sockread command.

on *:SockRead:sockname: {
  if ($sockerr) {
    echo $color(info) -sef Socket Error: $sock($sockName).wsmsg
    echo $color(info) -sef Socket Error Number: $sock($sockName).wserr Socket: $sockName
    return
  }
  else {
    ;my code goes here...
  }
}

 

Connection Terminated

It is possible for the remote end to terminate a connection, the same way you can /sockclose a connection early. When this happens the on sockClose event will trigger. The syntax is

on *:SockClose:sockname: {
  ;Your code goes here
}

The sockclose event will not trigger by you closing the connection, but ONLY by the remote end.

Secured Socket

I am sure you have visited websites with the padlock icon next to the URL in your browser. That icon indicated that website uses secure http (also known as HTTPS). The default port for HTTPS is 443.

SSL:

mIRC lets you create an SSL socket as well using the following syntax:

sockopen -e <sockName> <website> 443

The on sockopen and on sockread events are exactly the same as the regular http.

TLS:

mIRC does not yet support TLS sockets natively.

Multi-Sockets

When you create a script for a channel, for example a !google script. Using the method above you will only be able to initiate one socket at a time. If you recall, we actually /sockclose the socket to make sure it’s closed. Muti-Sockets is a method that will lets you open as many sockets as needed, not limiting it for one person to use at a time.

The basic idea of muti-socket is that every time the script initiates a new socket, it uses a unique name. This is usually done by appending a random sequence of numbers/letters to the end of the socket name.

For example, instead of using:

On *:Text:!google *:#: {
  sockClose google
  sockOpen google www.google.com 80
  sockMark google $EscapeURL($2-)
  set %google.chan $chan
}
on *:SockRead:Google: {
...

The problem with the example above is that only one person is able to use this google script at a time. If a second person types !google while another google socket is open, this script will close the socket of the first person and only reply to the second person.

An easy way to fix something like this is to add 6 number to the end of the socket way. For example we can do Google.###### where ###### are the 6 random. In the example below we use $ticks, which will ensure these 6 numbers will not show up for quite a while.

On *:Text:!google *:#: {
  var %sock google. $+ $right($ticks,6)
  sockClose %sock
  sockOpen %sock www.google.com 80
  sockMark %sock $EscapeURL($2-)
  set %google. $+ %sock $chan
}
on *:SockRead:Google.*: {
  ...

If you look closely at the sockread event, you will notice I have a google.* as the socket name, the * wildcard can be sued to represent "anything".

You now have to remember that because we are dealing with a dynamic socket name, all the variables will have to be dynamic as well. For example:

;to set a variable:
set %varName. $+ $sockname $noHTML(%x)
;to retrieve it
echo –s %varName. [ $+ [ $sockName ] ]
;or
echo -s $($+(%,varName,$sockname),2)

Making a dynamic variable called %varName.sockName.######

POST Method

PLease see the Advanced sockets tutorial.

mIRC UDP Sockets:

Please see the UDP socket tutorial.