LoginSignup
9

More than 3 years have passed since last update.

posted at

updated at

IRCの概要から簡易クライアント作成(Python3)まで

みなさんは、IRC(Internet Relay Chat)というワードを聞いたことはあるでしょうか。
IRCを使っているという人は少ないかもしれません(私の周りにも一人もいません)が、今回はこのIRCについて書いていきます!
(すでにIRCクライアントのコードが載っているQiitaの記事がありましたが、気になる部分がいくつかあったのと、Python2のコードだったので、改めて記事にしようと思いました。)

IRC(Internet Relay Chat)とは

IRCとは、サーバを介してクライアントクライアントが会話をする枠組みで、インスタントメッセンジャープロトコルの1つに分類されます。
また、ユーザクライアントソフトを実行し、サーバは情報の伝達を行うだけなので、サーバの負担が軽くチャットが高速に行えます。
チャットに特化したプロトコルなんですね!

なぜIRCの利用は衰退したの?

チャットアプリなんて、今はたくさんありますよね。(Facebook,Twitter,Discord,Slack,...etc.)
これらSNSを利用するほうが、IRCを利用するよりも便利で多機能で使いやすいわけですね。。。
また、SSL/TLSを利用せずにIRCを利用すると、暗号化していないため、第三者に会話の内容を盗み見られる可能性があります。(もちろんSSL/TLSを利用すれば安全です!)
LINEなどのサービスなら、暗号化などは自動でやってくれるから、そんなの普段は意識しないですよね。。

IRCを利用するメリットは?

それだけ聞くとIRCを利用するメリットはあまりないように感じるかもしれませんが、そんなことはありません!
まず、IRCは、データ通信に関するプロトコルが簡素で、かつオープンであるため、ユーザクライアントに用いるソフトウェアの開発が簡単なんです!今回の記事でも、簡易クライアントたった100行程度で作っちゃいます!
それだけではなく、IRCサーバを立てるのも、ngircdというものを利用すれば超簡単なんです!(今回はこれには触れません。)

今IRCは何に使われてるの?

では今、IRCは何に使われているんでしょうか。
それは、、、ボットネットの通信手段です。(誤解を招きそうなので言っておきますが、もちろん正規の普通の使い方でもたくさん利用されています。)
ボットネットとは、ボットウイルスに感染した端末で構成されるネットワークです。ボットウイルスとは、マルウェアの一種で、感染すると端末が悪意を持った第三者に遠隔操作され、不正アクセスDDoS攻撃などの犯罪行為に使われてしまいます。
その感染端末と攻撃者の通信手段に、IRCはよく使われています。。。
自分のパソコンをポートスキャンしたり、通信のログを見て、「IRC」という文字を見つけた場合は、ボットウイルスに感染している可能性があるかもしれません。。。

有名なIRCクライアントソフト

IRCクライアントソフトはたくさんありますが、ここでは中でも有名(人気)なもの(すべて無料)を紹介します。

LimeChat

おそらく1番有名(人気)なIRCクライアントソフトです。
Windowsの場合はLimeChat 2.xMacの場合はLimeChat for OSXを利用します。
現行IRCクライアントの中では最も安定しています。ヘルプが充実しているので初心者にもおすすめです。

HexChat

XChat(後述)ベースのソフトウェアです。
Windows用Mac用Unix系システム用があり、ほぼすべてのOSで利用できます。
海外のさまざまなネットワークが標準設定で入っています。

XChat

Linuxのほとんどのディストリビューションに標準で入っているIRCクライアントです。
Windows用Mac用Unix系システム用があり、ほぼすべてのOSで利用できます。
海外のさまざまなネットワークが標準設定で入っています。

Python3で簡易クライアントを作る

では早速IRCクライアントを作ってみましょう!
IRCクライアントを作るには、まずIRCコマンドというものについて知る必要があります。

IRCのコマンドについて

IRCにおいて、サーバクライアントは互いにメッセージを送信し合います。
また、メッセージが正しいコマンドを含んでいた場合、クライアントは仕様通りのリプライを期待します。
つまり、あるコマンドを含むメッセージに対するサーバからの応答メッセージの形式は決まっているわけですね。
このIRCコマンドはたくさんありますが、今回は、後で作成する簡易クライアントが実装している、最も基本的なコマンド8つについてのみ説明します。(コマンド正規表現で示します。)

PASSコマンド

PASS <password>

IRCサーバパスワードが設定されている場合は、ユーザは接続を開始しようとする前(NICK/USERの組み合わせを送る前)にPASSコマンドを送る必要があります。

NICKコマンド

NICK :<nickname>

ユーザニックネームを設定したり、今のニックネームを変更したりするのに使います。

USERコマンド

USER <username> <hostname> <servername> <realname>

新しいユーザユーザ名ホスト名本名を指定するために、接続のはじめに使われます。

QUITコマンド

QUIT (:<Quit Message>)??

クライアントセッションを終了します。サーバクライアントERRORメッセージを送ることでこれを承認します。

JOINコマンド

JOIN :<channel> ("," <channel>)* (<key> ("," <key>)*)??

ユーザによって、特定のチャンネルに接続するリクエストを行うために使用されます。

PRIVMSGコマンド

PRIVMASG <msgtarget> :<text to be sent>

ユーザ間のプライベートメッセージを送るのに使います。また、メッセージチャンネルに送るのにも使われます。msgtargetは通常、メッセージの受け取り手のニックネームか、チャンネル名です。
誰かがメッセージを送った場合は、サーバから次の形式でメッセージが送られてきます。

送られてくるとき
:<A's nickname>!(~<A's username>)??@<A's gateway-name> PRIVMSG <msgtarget> :<text to be sent>

PINGコマンド

PING :<server1> (<server2>)??

ネットワーク上のアクティブなクライアントサーバが、実際につながって動いているかどうかを確認するのに使われます。
サーバは、ネットワークからほかのアクションが届かない場合、一定間隔でPINGメッセージを送出します。
指定時間内にPINGメッセージに返答がない場合、その接続は閉じられます。
PINGメッセージを受け取ったら、server1(PINGメッセージを送ったサーバ)へのリプライとして、可能な限り早く正しいPONGメッセージを送らなくてはなりません。server2パラメータが指定されていれば、それがpingのターゲットとなり、メッセージはそこに転送されます。

PONGコマンド

PONG <server1> (<server2>)??

PINGメッセージへのリプライです。server2パラメータが指定されれば、メッセージはそこに転送されなくてはなりません。

IRCクライアント(Python3コード)

import sys, socket, os, signal

HOST = "X.X.X.X"
PORT = 6667 #IRCサーバでは一般的に6667番ポートが使われることが多い

BUF_SIZE = 1024

class IRC(object):
    def __init__(self):
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #socketオブジェクトの生成(TCP)

    def connect(self, host, port):
        self.server.connect((host, port)) #接続

    def login(self, password, nickname, username, realname, hostname = "hostname", servername = "*"):
        if password is not None: #中にはパスワードがいらないサーバもある
            pass_message = "PASS " + password + "\n" #PASSメッセージ
            self.server.send(pass_message.encode('utf-8')) #送信
        nick_message = "NICK " + nickname + "\n" #NICKメッセージ
        user_message = "USER %s %s %s :%s\n" % (username, hostname, servername, realname) #USERメッセージ
        self.server.send(nick_message.encode('utf-8')) #送信
        self.server.send(user_message.encode('utf-8')) #送信

    def join(self, channel):
        join_message = "JOIN " + channel + "\n" #JOINメッセージ
        self.server.send(join_message.encode('utf-8')) #送信

    def pong(self, server1, server2 = None):
        pong_message = "PONG %s %s" % (server1, server2) #PONGメッセージ
        pong_message += "\n"
        self.server.send(pong_message.encode('utf-8')) #送信

    def privmsg(self, channel, text):
        privmsg_message = "PRIVMSG %s :%s\n" % (channel, text) #PRIVMSGメッセージ
        self.server.send(privmsg_message.encode('utf-8')) #送信

    def quit(self):
        self.server.send(b"QUIT :bye!") #QUITメッセージ送信

    def handle_privmsg(self, prefix, text):
        print("\r" + prefix + ">" + text + "\n>", end="") #受信したPRIVMSGメッセージを処理、表示

    def wait_message(self):
        while(True):
            msg_buf = self.server.recv(BUF_SIZE) #受信
            msg_buf = msg_buf.decode('utf-8').strip()
            ## ここからメッセージ処理 ##
            prefix = None
            if msg_buf[0] == ":":
                p = msg_buf.find(" ")
                prefix = msg_buf[1:p]
                msg_buf = msg_buf[(p + 1):]

            p = msg_buf.find(":")
            if p != -1: #":"から始まるパラメータがまだあった場合
                last_param = msg_buf[(p + 1):]
                msg_buf = msg_buf[:p]
                msg_buf = msg_buf.strip()

            messages = msg_buf.split()
            ## ここまで ##

            command = messages[0] #コマンド名
            params = messages[1:] #今回は無視

            if command == "PING":
                self.pong(last_param) #PINGが来たらすぐPONGを返す
            elif command == "PRIVMSG":
                text = last_param #PRIVMSGコマンドで送られてきたメッセージ
                self.handle_privmsg(prefix, text)


    def client_interface(self, channel, prompt = ">"):
        while(True):
            line = input(prompt)

            if line == "quit":
                self.quit()
                break

            self.privmsg(channel, line)


def main():
    password = "password"
    nickname = "nickhoge"
    username = "usr"
    realname = "realname"
    channel = "#test_channel"

    irc = IRC()
    irc.connect(HOST, PORT)
    irc.login(password, nickname, username, realname)
    irc.join(channel)

    pid = os.fork() #子プロセス生成

    if(pid == 0): #os.fork()は、子プロセスでは0を返す
        irc.wait_message()
    else:
        irc.client_interface(channel)
        os.kill(pid, signal.SIGTERM) #子プロセスをkill(これをしないと子プロセスがゾンビプロセスになる)

if __name__ == "__main__":
    main()

macで実行してみた

実際にLimeChatユーザと会話してみました。
1.png
LimeChat側からみるとこんな感じです。
2.png
いい感じですね!

参考

https://qiita.com/mmttt202/items/d182c6f27f466c923fea
http://jbpe.tripod.com/rfcj/rfc2812.j.sjis.txt
https://www.friend-chat.jp/irc.html
https://wikiwiki.jp/2chIRC/IRC%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E7%B4%B9%E4%BB%8B
https://ja.wikipedia.org/wiki/Internet_Relay_Chat
https://murashun.jp/blog/20190215-01.html

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9