Luigi Auriemma

aluigi.org (ARCHIVE-ONLY FORUM!)
It is currently 19 Jul 2012 14:54

All times are UTC [ DST ]





Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 
Author Message
 Post subject: [c#]Help with ventrilo/udp
PostPosted: 19 Apr 2008 04:07 

Joined: 19 Apr 2008 00:59
Posts: 42
Well I got the tcp for ventrilo down but the udp is giving problems.

Its a bit messy right now (trying to get it to just work), but this is the code.

Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;

namespace Vent_Algos
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        private static extern int GetTickCount();
        byte[] EC_Key = { 0xAA, 0x55, 0x22, 0xCC, 0x69, 0x7C, 0x38, 0x91, 0x88, 0xF5, 0xE1 };
        void EncryptConnect(ref byte[] buf) //Encrypt and decrypt
        {
            if (buf.Length < 1)
                return;
            int q = 0;
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] += (byte)(EC_Key[q] + (i % 0x1B));
                q = (q + 1) % 0xB;
            }
        }
        void DecryptConnect(ref byte[] buf) //Encrypt and decrypt
        {
            if (buf.Length < 1)
                return;
            int q = 0;
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] -= (byte)(EC_Key[q] + (i % 0x1B));
                q = (q + 1) % 0xB;
            }
        }
        int E_Step = 0;
        void Encrypt(ref byte[] buf,byte[] Key) //Encrypt and decrypt
        {
            if (buf.Length < 1)
                return;
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] += (byte)(Key[E_Step] + (i % 0x2D));
                E_Step = ((E_Step + 1) % Key.Length);
            }
        }
        int D_Step = 0;
        void Decrypt(ref byte[] buf, byte[] Key) //Encrypt and decrypt
        {
            if (buf.Length < 1)
                return;
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] -= (byte)(Key[D_Step] + (i % 0x2D));
                D_Step = ((D_Step + 1) % Key.Length);
            }
        }
        int MorphedTime(ref int time)
        {
            time = (time * 0x343FD) + 0x269EC3;
            return (time >> 0x10) & 0x7FFF;
        }
        int Unknown(int t,byte[] Key)
        {
            t = t | (t << 0x8);
            t = t | (t << 0x10);
            int i = 0;
            int t1;
            int t2;
            int t3;
            int t4;
            int t5;
            t3 = t;
        again:
            t2 = BitConverter.ToInt32(Key,i);
            t5 = 0x7EFEFEFF;
            t1 = t2;
            t4 = t5;
            t2 = t2 ^ t3;
            t4 += t1;
            t5 += t2;
            t2 = (int)(t2 ^ 0xFFFFFFFF);
            t1 = (int)(t1 ^ 0xFFFFFFFF);
            t2 = t2 ^ t5;
            t1 = t1 ^ t4;
            i+=4;
            t2 = (int)(t2 & 0x81010100);
            if (t2 != 0)
            {
                t1 = BitConverter.ToInt32(Key,i - 4);
                t = t & 0xFF;
                if (t == (t1 & 0xFF))
                    return i - 4;
                if ((t1 & 0xFF) == 0)
                    return 0;
                if (t == ((t1 & 0xFF00) >> 8))
                    return i - 3;
                if (((t1 & 0xFF00) >> 8) == 0)
                    return 0;
                t1 = t1 >> 0x10;
                if (t == (t1 & 0xFF))
                    return i - 2;
                if ((t1 & 0xFF) == 0)
                    return 0;
                if (t == ((t1 & 0xFF00) >> 8))
                    return i - 1;
                if (((t1 & 0xFF00) >> 8) == 0)
                    return 0;
                goto again;
            }
            t1 = (int)(t1 & 0x81010100);
            if (t1 == 0)
                goto again;
            t1 = t1 & 0x1010100;
            if (t1 != 0)
                return 0;
            t4 = (int)(t4 & 0x80000000);
            if (t4 != 0)
                goto again;
            return 0;
        }
        byte[] Unknown_Key = { 0x2C,0x23,0x27,0x60,0x2D,0x2B,0x2E,0x5F,0x7C,0x2F,0x3D,0x3A,0x22,0x5C,0x00,0x00};
        byte[] GenerateKey(ref int time,int size)
        {
            byte[] ret = new byte[size];
            int t = 0;
            for (int i = size-1; i > -1; i--)
            {
            tryagain:
                t = MorphedTime(ref time);
                t = (int)(t & 0x8000007F);
                if (t < 0)
                {
                    t--;
                    t = (int)(t | 0xFFFFFF80);
                    t++;
                }
                if (t < 0x21)
                    goto tryagain;
                if (t > 0x7F)
                    goto tryagain;
                if (Unknown(t, Unknown_Key) != 0)
                    goto tryagain;
                ret[i] = (byte)t;
            }
            return ret;
        }
        string _Version = "3.0.0";
        string _Version2 = "3.0.1";
        TcpClient TC;
        byte[] E_Key;
        byte[] D_Key;
        short Endian(short num)
        {
            return (short)((num << 8) | (num >> 8));
        }
        ushort Endian(ushort num)
        {
            return (ushort)((num << 8) | (num >> 8));
        }
        int Endian(int num)
        {
            byte[] t = BitConverter.GetBytes(num);
            Array.Reverse(t);
            return BitConverter.ToInt32(t, 0);
        }
        uint Endian(uint num)
        {
            byte[] t = BitConverter.GetBytes(num);
            Array.Reverse(t);
            return BitConverter.ToUInt32(t, 0);
        }
        void Send(byte[] buf)
        {
            byte[] t = new byte[buf.Length + 2];
            BitConverter.GetBytes((short)Endian((short)buf.Length)).CopyTo(t, 0);
            Encrypt(ref buf, E_Key);
            Array.Copy(buf, 0, t, 2, buf.Length);
            TC.Client.Send(t);
        }
        void SendConnect(byte[] buf)
        {
            state = 0;
            E_Step = 0;
            D_Step = 0;
            byte[] t = new byte[buf.Length + 2];
            BitConverter.GetBytes((short)Endian((short)buf.Length)).CopyTo(t, 0);
            EncryptConnect(ref buf);
            Array.Copy(buf, 0, t, 2, buf.Length);
            TC.Client.Send(t);
        }
        int state = 0;
        bool ParsePacket(ref byte[] buf)
        {
            if (state == 0)
            {
                DecryptConnect(ref buf);
                state++;
            }
            else
            {
                Decrypt(ref buf, D_Key);
            }
            switch (BitConverter.ToInt32(buf, 0))
            {
                case (0x6):
                    byte[] key = new byte[buf.Length - 12];
                    Array.Copy(buf, 12, key, 0, key.Length);
                    int len = Unknown(0x7C, key);
                    E_Key = new byte[len];
                    int i = len;
                    while (i < (buf.Length - 12))
                    {
                        if (key[i] == 0)
                        {
                            break;
                        }
                        i++;
                    }
                    i--;
                    D_Key = new byte[i - len];
                    Array.Copy(key, 0, E_Key, 0, len);
                    Array.Copy(key, len + 1, D_Key, 0, i - len);

                    byte[] p = new byte[0x118];
                    p[0] = 0x48;
                    p[4] = 2;
                    p[0xC] = 0x7F;
                    p[0xF] = 1;
                    BitConverter.GetBytes(0x1F137).CopyTo(p, 0x10);
                    p[0x16] = 1;
                    Array.Copy(new byte[] { 0xBE, 0x31, 0x82, 0x95, 0x6E, 0xEA, 0xCF, 0x91, 0x0D, 0x0F, 0x98, 0xA1, 0xAB, 0x44, 0x4B, 0x76 },0,p,0x18,0x10);

                    Encoding.ASCII.GetBytes(_Version2).CopyTo(p, 0x28);
                    Encoding.ASCII.GetBytes(_Version).CopyTo(p, 0x68);
                    Encoding.ASCII.GetBytes("High").CopyTo(p, 0x98);
                    Encoding.ASCII.GetBytes("WIN32").CopyTo(p, 0xD8);

                    Send(p);

                    break;
                case (0x37): //Ping PONG!
                    Send(buf);
                    break;
                default:
                    break;
            }
            return true;
        }
        bool ParsePacketFrom(ref byte[] buf)
        {
            switch (BitConverter.ToInt32(buf, 0))
            {
                default:
                    break;
            }
            return true;
        }
        byte[] RollOver = new byte[0];
        void Recv(IAsyncResult ar)
        {
            if (ar == null || TC == null)
                return;
            byte[] buf = (byte[])ar.AsyncState;
            int read = TC.Client.EndReceive(ar);
            if (read > 0)
            {
                Array.Resize(ref buf, read);
                if (RollOver.Length > 0)
                {
                    byte[] t = new byte[buf.Length + RollOver.Length];
                    Array.Copy(RollOver, t, RollOver.Length);
                    Array.Copy(buf, 0, t, RollOver.Length, buf.Length);
                    buf = t;
                }
                ushort start = 0;
                int i = 0;
                while (i < read)
                {
                    start = Endian(BitConverter.ToUInt16(buf, i));
                    if (start + i > read)
                    {
                        Array.Resize(ref RollOver, read - i);
                        Array.Copy(buf, i, RollOver, 0, read - i);
                        break;
                    }
                    byte[] packet = new byte[start];
                    Array.Copy(buf, i + 2, packet, 0, start);
                    if (!ParsePacket(ref packet))
                        return;
                    i += start + 2;
                }

            }
            buf = new byte[0x200];
            TC.Client.BeginReceive(buf, 0, 0x200, SocketFlags.None, Recv, buf);
        }
        byte[] RollOverFrom = new byte[0];
        void RecvFrom(IAsyncResult ar)
        {
            if (ar == null || UC == null)
                return;
            passa p = (passa)ar.AsyncState;
            byte[] buf = p.bytes;
            EndPoint ep = p.ep;
            int read = UC.Client.EndReceiveFrom(ar, ref ep);
            if (read > 0)
            {
                Array.Resize(ref buf, read);
                if (RollOverFrom.Length > 0)
                {
                    byte[] t = new byte[buf.Length + RollOverFrom.Length];
                    Array.Copy(RollOverFrom, t, RollOverFrom.Length);
                    Array.Copy(buf, 0, t, RollOverFrom.Length, buf.Length);
                    buf = t;
                }
                ushort start = 0;
                int i = 0;
                while (i < read)
                {
                    start = Endian(BitConverter.ToUInt16(buf, i+0xA));
                    if (start + i > read)
                    {
                        Array.Resize(ref RollOverFrom, read - i);
                        Array.Copy(buf, i, RollOverFrom, 0, read - i);
                        break;
                    }
                    byte[] packet = new byte[start];
                    Array.Copy(buf, i, packet, 0, start);
                    if (!ParsePacketFrom(ref packet))
                        return;
                    i += start;
                }

            }
            buf = new byte[0x200];
            UC.Client.BeginReceiveFrom(buf, 0, 0x200, SocketFlags.None,ref ep, RecvFrom, buf);
        }
        void DecryptUDP(ref byte[] buf,uint keyy)
        {
            byte[] key = BitConverter.GetBytes(keyy);
            for (int i = 0x10; i < Math.Min(buf.Length, 200); i++)
            {
                buf[i] -= key[i & 3];
            }
        }
        void EncryptUDP(ref byte[] buf, uint keyy,byte idx)
        {
            byte[] key = BitConverter.GetBytes(keyy);
            idx = (byte)((key[0] & 0xF) * idx);
            for (int i = 0x10; i < Math.Min(buf.Length, 200); i++)
            {
                buf[i] += key[(idx+i) & 3];
            }
        }
        byte[] EncryptUDP(byte[] buf, uint keyy,byte idx)
        {
            byte[] ret = new byte[buf.Length];
            buf.CopyTo(ret, 0);
            byte[] key = BitConverter.GetBytes(keyy);
            idx = (byte)((key[0] & 0xF) * idx);
            for (int i = 0x10; i < Math.Min(buf.Length, 200); i++)
            {
                ret[i] += key[(idx + i) & 3];
            }
            return ret;
        }

        EndPoint EP;
        UdpClient UC;
        struct ventrilo3_auth
        {
            public uint key;
            public int port;
            public ventrilo3_auth(uint k, int p)
            {
                key = k;
                port = p;
            }
        }
        struct passa
        {
            public EndPoint ep;
            public byte[] bytes;
            public passa(EndPoint e,byte[] b)
            {
                ep = e;
                bytes = b;
            }
        }
        ventrilo3_auth[] va = {
            new ventrilo3_auth( 0x48332e1f,   6100 ),
            new ventrilo3_auth( 0x4022b2b2, 6100 ),
            new ventrilo3_auth( 0x3dc24a36,  6100 ),
            new ventrilo3_auth( 0x46556ef2, 6100 )
        };
        private void Form1_Load(object sender, EventArgs e)
        {
            TC = new TcpClient();
            UC = new UdpClient();
            TC.Connect("127.0.0.1", 3784);
            UC.Connect("127.0.0.1", 3784);
            EP = UC.Client.RemoteEndPoint;
            byte[] p = new byte[0x54];
            Encoding.ASCII.GetBytes(_Version).CopyTo(p,4);
            int time = GetTickCount();
            Array.Copy(GenerateKey(ref time,0x1F),0,p,0x14,0x1F);
            Array.Copy(GenerateKey(ref time,0x1F),0,p,0x34,0x1F);
            SendConnect(p);
            byte[] p2 = { 0x00, 0x00, 0x00, 0x00, 0x55, 0x44, 0x43, 0x4C, 0x00, 0x04, 0x00, 0xC8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x65, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            byte[] p3 = { 0x00, 0x00, 0x00, 0x00, 0x55, 0x44, 0x43, 0x4C, 0x00, 0x05, 0x00, 0x84, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0xC5, 0x03, 0x4B, 0x7E, 0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x7E, 0x03, 0x22, 0x47, 0xB0, 0x95, 0x77, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFC, 0x00, 0x00, 0x4B, 0x7E, 0x03, 0x22, 0x72, 0x19, 0xF6, 0x87, 0x3E, 0xAE, 0x6D, 0x61, 0xD3, 0x7A, 0x87, 0xF1, 0xE2, 0x61, 0x54, 0x2B, 0x57, 0x49, 0x4E, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x2E, 0x30, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            UC.Client.SendTo(p2, EP);
            byte[] buf2 = new byte[0x200];
            UC.Client.BeginReceiveFrom(buf2, 0, 0x200, SocketFlags.None, ref EP, RecvFrom, buf2);
            for (int i = 0; i < 4; i++)
            {
                EndPoint ep = new IPEndPoint(va[i].key, va[i].port);
                UC.Client.SendTo(EncryptUDP(p3, Endian(va[i].key), (byte)(i+1)), ep);
                byte[] buf3 = new byte[0x200];
                passa pp = new passa(ep, buf3);
                UC.Client.ReceiveFrom(buf3,ref ep);
            }
            byte[] buf = new byte[0x200];
            TC.Client.BeginReceive(buf, 0, 0x200, SocketFlags.None, Recv, buf);
            //byte[] buf2 = new byte[0x200];
            //UC.Client.BeginReceiveFrom(buf2, 0, 0x200, SocketFlags.None, ref EP, RecvFrom, buf2);

        }
    }
}


Yes I know its messy/etc, just trying to get it working, then plan on rewriting.

So first recvfrom packet is received but the other 4 aren't.


Top
 Profile  
 
 
 Post subject:
PostPosted: 19 Apr 2008 10:19 

Joined: 13 Aug 2007 21:44
Posts: 4068
Location: http://aluigi.org
uhmm it's a bit hard to help you since my mind is not able to support more than 4 lines of code 8-)

Anyway if you the decryption of the second packet fails means you have not correctly implemented (or you are using the wrong key) the ventrilo_dec() function.
For example at a quick look I don't see the "(i % 45)" needed for the decryption routine which is really very simple (similar to ventrilo_first_dec plus the saving of the key positions for reusing them with the subsequent packets).


Top
 Profile  
 
 Post subject:
PostPosted: 19 Apr 2008 14:34 

Joined: 19 Apr 2008 00:59
Posts: 42
aluigi wrote:
uhmm it's a bit hard to help you since my mind is not able to support more than 4 lines of code 8-)

Anyway if you the decryption of the second packet fails means you have not correctly implemented (or you are using the wrong key) the ventrilo_dec() function.
For example at a quick look I don't see the "(i % 45)" needed for the decryption routine which is really very simple (similar to ventrilo_first_dec plus the saving of the key positions for reusing them with the subsequent packets).


Well I am still just trying to get the sendto recvfrom syncing to work first. I know I still need to fixup some functions. Its that the program is only receiving the first recvfrom packet and not the other 4. Which in 1 of the 4 received I need a key to connect to the server.

When it sends the second packet you can see

Array.Copy(new byte[] { 0xBE, 0x31, 0x82, 0x95, 0x6E, 0xEA, 0xCF, 0x91, 0x0D, 0x0F, 0x98, 0xA1, 0xAB, 0x44, 0x4B, 0x76 },0,p,0x18,0x10);

which is received in 1 of the recvfrom packets which is why its not working.


Top
 Profile  
 
 Post subject:
PostPosted: 19 Apr 2008 17:48 

Joined: 13 Aug 2007 21:44
Posts: 4068
Location: http://aluigi.org
Ah ok, I thought you were referring to the Ventrilo packets.

Anyway yes only one or two master servers replies with the key for joining Ventrilo 3.x servers, so it's enough to take one of the key you received and the number of the master server which sent it (the number is the position in the ventrilo3_auth structure).
These 2 data are all you need.


Top
 Profile  
 
 Post subject:
PostPosted: 19 Apr 2008 17:51 

Joined: 19 Apr 2008 00:59
Posts: 42
So back to the problem. I think it's probably because the packets aren't done right, but doesn't make sense... It sends exactly like the ventrilo server does 0,o...


Top
 Profile  
 
 Post subject:
PostPosted: 26 Apr 2008 17:50 

Joined: 19 Apr 2008 00:59
Posts: 42
Ok so looked back into ventrilo and I noticed it only connects once. So my question is, does it send both tcp/udp over the same connection?

So it create 2 udp sockets on startup that it uses. But I noticed it never connects to anything with them (using winsock api connect). How does sendto work exactly? I am getting confused by it.


Top
 Profile  
 
 Post subject:
PostPosted: 26 Apr 2008 18:04 

Joined: 13 Aug 2007 21:44
Posts: 4068
Location: http://aluigi.org
UDP is connectionless so connect() is not needed (is possible to use it but this is another story) because doesn't exist an UDP connection.
The additional 2 parameters in sendto() are just the informations about where sending the packet (IP, port and family).

In ventrilo the UDP part is used only to know if the server is up, some informations and naturally all the data needed to get the server's hash through the subsequent sending of the packets to the centralized master servers.


Top
 Profile  
 
 Post subject:
PostPosted: 26 Apr 2008 18:16 

Joined: 19 Apr 2008 00:59
Posts: 42
Ugh, I am not getting how ventrilo uses sendto. Still can't get a response from the server with udp. I compared the packets being sent from my client and ventrilo and they are exactly the same when encrypted (except for the int at 0x10).


Top
 Profile  
 
 Post subject:
PostPosted: 26 Apr 2008 19:53 

Joined: 13 Aug 2007 21:44
Posts: 4068
Location: http://aluigi.org
the packet you send to the server is not encrypted (in my ventrilo3_handshake.c in fact I use 0 as key), only those sent to the centralized master servers use the keys


Top
 Profile  
 
 Post subject:
PostPosted: 27 Apr 2008 04:26 

Joined: 19 Apr 2008 00:59
Posts: 42
aluigi wrote:
the packet you send to the server is not encrypted (in my ventrilo3_handshake.c in fact I use 0 as key), only those sent to the centralized master servers use the keys


first packet it sends isn't encrypted the other 4 are.


Top
 Profile  
 
 Post subject:
PostPosted: 30 May 2008 05:43 

Joined: 19 Apr 2008 00:59
Posts: 42
Does it send the 4 packets to the server you want to connect or another?


Top
 Profile  
 
 Post subject:
PostPosted: 30 May 2008 09:45 

Joined: 13 Aug 2007 21:44
Posts: 4068
Location: http://aluigi.org
No, the 4 udp packets must be sent to the centralized servers which will reply with 4 different hashes to use with the Ventrilo server 3.x you want to join (that's why you must specify the number of centralized server which replied followed by the hash when you connect to the target server)


Top
 Profile  
 
 Post subject: Re: [c#]Help with ventrilo/udp
PostPosted: 03 Apr 2010 04:07 

Joined: 02 Feb 2009 06:29
Posts: 13
ik this is old as fuck...but did you ever get this working?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 

All times are UTC [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for: