2015年3月5日木曜日

【CentOS6.5】【Postfix】【Dovecot】メールサーバのクイックセットアップ②

その①から続いて、

③ Dovecotの設定


/etc/dovecot/conf.d/10-mail.conf

     mail_location = maildir:~/Maildir

       (/etc/postfix/main.cfの設定がMailboxだった場合は、
    mail_location = mbox:~/mail:INBOX=/var/mail/%u
         その場合、/var/mail/%uのアクセス権限を変更する必要あり)

/etc/dovecot/conf.d/10-auth.conf

     disable_plaintext_auth = no

       平文でのパスワード認証を禁止するか設定
       テストの段階では、暗号化はしないのでとりあえずnoに設定

Dovecot を起動(既に動いていれば、再起動)する。
# service dovecot start

④ telnetで接続して動作確認してみる。


Dovecot(POPサーバ)への接続

→ ログイン&受信メールの確認をしてみる。
→ 予め『testuser』というユーザ用のアカウントを作成し、パスワードも付与。
  # useradd testuser
    # passwd testuser

 $ telnet localhost 110
 Trying ::1...
 Connected to localhost.
 Escape character is '^]'.
 +OK Dovecot ready.
 USER testuser  (←『 USER サーバのユーザアカウント』 を入力)
 +OK
 PASS xxxxxx  (←『 PASS アカウントのパスワード』 を入力)
 +OK Logged in.
 LIST (←受け取ったメールを確認する)
 +OK 3 messages: (← 予めメールを送っておけば、届いているのが確認できる)
 1 1060 (← 3通のメールが届いている)
 2 1113
 3 1852.
 TOP 1 (←1番目のメールのヘッダを表示)
 (省略)
 RETR 1 (←1番目のメールのボディを表示)
 (省略)
 QUIT  (← telnet から抜ける。)
 +OK Logging out.
 Connection closed by foreign host.

Postfix(SMTPサーバ) への接続

→ メールを送信してみる。

 $ telnet localhost 25
 Trying ::1...
 Connected to localhost.
 Escape character is '^]'.
 220 mail.xxx.mydns.jp ESMTP Postfix
 EHLO mail.xxx.mydns.jp (← 『EHLO ホスト名』を入力)
 250-mail.xxx.mydns.jp
 250-PIPELINING
 250-SIZE 10240000
 250-VRFY
 250-ETRN
 250-ENHANCEDSTATUSCODES
 250-8BITMIME
 250 DSN
 MAIL FROM:<testuser@xxx.mydns.jp> (← 『MAIL FROM:<送信元アドレス>』を入力) 
 250 2.1.0 Ok
 RCPT TO:<送信先のメールアドレス> (← 『RCPT TO:<送信先のメールアドレス>』を入力)
 250 2.1.5 Ok
 DATA
 354 End data with <CR><LF>.<CR><LF>
 Test (←メール本文を入力する)
 . (←メール本文を終了する)
 250 2.0.0 Ok: queued as 860ADBF609
 QUIT  (← telnet から抜ける。)
 221 2.0.0 Bye
 Connection closed by foreign host.

⑤ MUA(Thunderbird)を使って送受信してみる。


ツール > アカウント設定 > アカウント操作 > メールアカウントを追加

メールアドレスは、『 ユーザアカウント@mydomain 』
パスワードは、アカウントのパスワード


続けるを押下した後、手動設定


サーバのホスト名等を入力して、再テスト → 完了


セキュリティ警告が出てくるが、取り敢えず了解しておく。

試しに他のメールアカウントと送受信してみる。 

受信は問題なく行えるはずだが、送信に関しては、私の様にVPSを使って外部のネットワーク
から接続している場合、“Relay access denied” となって、送信に失敗する。

これは、/etc/postfix/main.cf がデフォルトで

smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
(この設定はmain.cfには記入されていないが、postconf -d コマンドで確認できる)

と設定されており、外部ネットワークからのリレーを許可しないからだ。
送信メール認証の設定後、下記の様に設定変更すれば、送信可能になる。

smtpd_recipient_restrictions = permit_mynetworks,
                                                  permit_sasl_authenticated,
                                                  (認証を通過すればリレーを許可)
                                                  reject_unauth_destination

※VPSではなく自宅のサーバでも同様の事象が起こったので、上記の説明は誤っている可能性が高い。但し、自宅サーバでも認証設定後には正常に送信できた。(2016/05/06追記)

この段階で、どうしても送信テストしたい場合は、main.cf のmynetworks に、MUAを使うクライアントのIPアドレスを追加してやれば送信できる。ただし、ダイナミックIPアドレスの場合、IPアドレスが変わってしまうので、一時的なテストに留めておいた方が良いと思う。

最小限の設定は以上。
ここから、暗号化や認証等の設定を追加していく。

【CentOS6.5】【Postfix】【Dovecot】メールサーバのクイックセットアップ①

メールサーバの再セットアップも、ひと通り終わったので、設定についてメモしておく事にした。

(※2016/05/06 一部加筆)


まずは最小限の設定で、『これだけやっておけば外部とメールが送受信できる』というところまで、
設定する。

ここまでできれば、ネットワークやiptables等の、メールソフト以外の設定には問題ない事が分かるので、後ほど認証や暗号化等の設定を追加した際にも、問題の切り分けがしやすい、はず。

環境は以下の通り。

OS : CentOS6.5
MTA : postfix2.6.6
MRA : dovecot2.0.9
MUA : Thunderbird
DNS : myDNS

ドメイン名(例) = xxx.mydns.jp (xxxは伏せ字)
ホスト名(例) = mail.xxx.mydns.jp (xxxは伏せ字)


  • 名前解決にはダイナミックDNSサービスの MyDNS を使っているので、下記のMXレコードの設定前に、登録と初期設定を行っておく必要がある。
  • また、ポート開放についてもここでの説明は割愛しているが、適宜サーバのiptablesやファイアウォールの設定を事前に行う必要がある。


① myDNSの設定


MXレコードとAレコードに、メールサーバのホスト名を入力する。
(他に、IPアドレスの設定は事前にやっておく)

※MXレコードの欄は、FQDNで登録しないとうまく動作しなかった。



② postfixの設定

postfixの設定ファイル 『/etc/postfix/main.cf』 の下記項目を編集する。
なお、デフォルト値はpostconf -d で確認できる。

myhostname = mail.xxx.mydns.jp

        メールサーバのホスト名(FQDN)を記入。
        DNSのMXレコード、Aレコードの設定と一致させる。

      myorigin = $mydomain (行頭の"#(コメントアウト)"を外す)

        送信メールに付加される送信元アドレスの@以降の形式を指定
        (myhostname か mydomain)。
        mydomainに設定することで、送信元メールアドレスは、foo@xxx.mydns.jpになる。

inet_interfaces = all

        外部のメールサーバから送られてくるメールを受け取る。

mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

        他のメールサーバに転送を行わない(=このメールサーバが受信する)ホスト、ドメイン
        を指定する。$mydomain を含めておくことで、foo@xxx.mydns.jp宛のメールを受信できる。

      home_mailbox = Maildir/ (または Mailbox)を指定

        メールを保存する場所を設定。
        Mailbox 形式は、全てのメールを1つのファイルに保存する。
        Maildir/ 形式は、1つのメール=1つのファイルになる。パフォーマンスやセキュリティ等、
        Maildir/ の方が優れている、らしい。

         Mailbox を指定した場合は、さらにメールスプールディレクトリを指定する。
         → (mail_spool_directory = /var/mail または /var/spool/mail)

   ※Maildir/ ディレクトリは、各ユーザのホームディレクトリ配下に自動で生成される。   

以下は基本的にはデフォルト値のままでもいいが、場合によっては変更する。

      mydomain = xxx.mydns.jp

        何も指定しなければ、myhostnameの最初の".(ドット)"までが削除された値が指定される。
        myhostname = mail.xxx.mydns.jp であれば自動的に mydomain = xxx.mydns.jp となる。
        (ホスト名がaaa.bbb.xxx.mydns.jp のような場合は変更する)
      

      mynetworks_style = subnet

      mynetworks = aaa.bbb.ccc.0/dd(自ホストのネットワーク), 127.0.0.0/8(自ホスト)

        メールサーバを利用可能なクライアントのネットワークを設定。
        上記は両者とも自ホストと、自ホストのネットワークを指定している。
        両方記載した場合には、mynetworks が優先される。
        (自ホストのネットワークを明示したい場合には、mynetworksに記載する。)



inet_interfaces を変更した際はreloadではなく再起動が必要。
main.cf の編集後、忘れずにpostfixを再起動させる。

# service postfix restart

長くなってきたので、次回に続く。

2015年3月3日火曜日

【CentOS6.5】@myhostname宛のメールは受信できるが、@mydomain宛のメールが受信できない その②

昨日の続きです。

怪しそうな設定箇所を見なおしても特に問題なく、Postfixのログをみて、ググったりしても良く分からなかった(受け取り拒否しているような痕跡もない)。

「ネットワークの設定に問題があるのかな?」と思いネットワークの設定ファイルを少しづつ変えてトライし続ける。起こっている事象から原因を特定できればいいのだが、私の様な未熟者は、取り敢えず手当たり次第に引っ掻き回して、上手くいったら、そこから理由を逆算するしかない。

私が使っているVPS 『ConoHa』は、スナップショット機能があるので、変な設定になっても一発で元に戻せる。こういう実験をする時には便利だ。

/etc/sysconfig/network
/etc/sysconfig/network-scripts/ifcfg-eth
/etc/resolv.confi

あたりを書いたり消したり、、、

挙動は変わらない。

やっぱり何かDNSの設定がおかしいのかも?と思い、myDNSの設定を変えてトライしてみる事にした。

DOMAIN INFO

myDNSの設定項目は上の通りだ。

私の設定では

Domain:hoge.mydns.jp(hogeは適当な文字列)
MXレコード : mail(メールサーバのホスト名)
Aレコード : server(サーバのホスト名)
Aレコード : www(ウェブサーバのホスト名)
Aレコード : mail(メールサーバのホスト名)

としていた。

ここで、

MXレコードをホスト名でなく、FQDNで指定してみた。 ⇒ @mydomainで届く。解決!
MXレコードを未指定(自動補完)にしてみた。 ⇒ @mydomainで届く。解決!

という訳で、MXレコードの記載が問題だったようだ。

具体的には、MXレコードにホスト名を記載すると、ホスト名の後にドメインが自動補完されると思っていたが、実際にはされていなかった?ようだ。

と、いうわけで、解決法は、MXレコードには、FQDNで記載するという事になる。

しかしmyDNSの設定について解説しているサイトでは、ホスト名だけの指定で解説されているし、設定項目の説明にも"hostname"と書かれている。私自身、以前はホスト名だけの指定で問題なかった。

何か変更があったのか、他の人にも起こっている事なのか、いつからそうなのか、謎である。

=====

ここから先は、自分の頭の整理のために、今回のトラブルの際に、名前解決で何が起こっていたのかを推測して書いていく。

ケース1 foo@hoge.mydns.jp 宛の場合


① foo@hoge.mydns.jp 宛にメールを送る。

② DNSサーバがMXレコードを参照して、メールサーバのFQDNを調べる。

 ③-A MXレコードに[mail.hoge.mydns.jp]と記載していた場合

  Aレコードを参照して、ホスト名がmail=FQDNが[mail.hoge.mydns.jp]が見つかる。

  Aレコードに記録されている[mail.hoge.mydns.jp]のIPアドレスへ配送する。

 ③-B MXレコードに[mail]と記載していた場合

  Aレコードを参照するが、FQDNが[mail]は見つからないので、配送できない。


ケース2 foo@mail.hoge.mydns.jp 宛の場合


① foo@mail.hoge.mydns.jp 宛にメールを送る。

② DNSサーバがMXレコードを参照して、メールサーバのFQDNを調べる。

 ③-A MXレコードに[mail.hoge.mydns.jp]と記載していた場合

  ケース1と同じ

 ③-B MXレコードに[mail]と記載していた場合

  Aレコードを参照するが、FQDNが[mail]は見つからないので、配送できない。

④ MXレコードからの参照とは別に、Aレコードを参照。
  @以下(mail.hoge.mydns.jp)と一致するAレコードが見つかり、指定のIPアドレスへ配送される。


ケース3 MXレコードを未指定(自動補完)の場合

① MXレコードには、ドメイン名(hoge.mydns.jp)が記述される。

② ドメイン名は、IPアドレスに紐付けられている。

② foo@hoge.mydns.jp宛のメールは、ドメイン名と紐付けられているIPアドレスへ配送される。
 (Aレコード未記載でも届くので、Aレコードは参照されていない?)
 (foo@XXX.hoge.mydns.jp 宛のメールは、Aレコードにホスト名XXXが記録されていれば届く。)



あくまで推測です。













【CentOS6.5】@myhostname宛のメールは受信できるが、@mydomain宛のメールが受信できない その①

現在、メールサーバの再構築中。

昨年夏にメールサーバをつくった時のメモ書きが残っているので、それに従えば概ねうまくいっている。

それでも、思いがけないトラブルが現れるもので、表題の通り

『@myhostname宛のメールは受信できるが、@mydomain宛のメールが受信できない』

という状況に陥った。

詳しい状況は次のとおりだ。

使っているソフトは、
postfix : 2.6.6
dovecot : 2.0.9

ドメイン名についてはmydnsを使っていて、下記の例の用に設定している。

domain : hoge.mydns.jp
hostname : mail.hoge.mydns.jp

既存の他のメールアドレスから新規メールを送信したとき、

foo@mail.hoge.mydns.jp ⇒ 届く ◯
foo@hoge.mydns.jp ⇒ 届かない ✕

という状況だ。
この状況で一番考えられそうなのは、

postfix の /etc/postfix/main.cf 内の設定項目 mydestination の設定を間違っていることだ。

しかし、

mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

としていて、この設定には問題無い。

送信元の MAILER-DAEMON から返ってきたメッセージを見てみる。

(一部抜粋)
=====
<foo@hoge.mydns.jp>: host mail[xxx.xxx.xxx.xxx] said: 554 5.4.0
    Error: too many hops (in reply to end of DATA command)
=====

中継が多すぎるとの事で、ちゃんと配送されずに、どっかで堂々巡りになっている?ようだ。

mydns の設定がまずくて、ちゃんと名前解決されていないという可能性も考えたが、mydnsの設定はややこしいものではないし、前回、上手くいっていた時と同様に設定できたいたので、サーバー内のソフトやネットワークの設定ファイルの見直しにとりかかった。

そしてドハマりした。

続く。




2015年3月1日日曜日

【Android】【CountDownTimer】カウントダウンタイマーを、1つのボタンで開始、一時停止、リセットする。

現在タイマーを使ったアプリを作成している。

CountDownTimer というクラスが用意されているので、それを使えばタイマー作成には結構便利。

CountDownTimer は、そのままだと一時停止が使えないので、一時停止するにはちょっと工夫が必要だ。そのやり方については、こちらの記事を参考にさせて頂いた。

ただ、参考元のやり方そのままだと、ボタンが増えて画面がちょっと煩雑になってしまう。

ストップウォッチ感覚で直感的に操作できるようにしたいので、スタート、ストップは同じボタンにしたい。

Androidには標準で『トグルボタン』という、ON/OFFを切り替えられる便利なボタンがあるので、それを利用してタイマーを自分なりに再作成してみた。



トグルボタンは、AndroidStudioのパレットからドラッグ・アンド・ドロップでXML上に配置できる。


配置したトグルボタンは、JavaでOnCheckedChangeListener に関連付け、下記の様に、if (isChecked) - else で分けて、ON/OFF時の処理をそれぞれ記載する。

        toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                
                if (isChecked) {

                    // トグルボタンがONの際の処理を記述
                    
                } else {

                    // トグルボタンがOFFの際の処理を記述

                }
            }
        });

今回のタイマーでは、

[ON] タイマーをインスタンス化 → スタート
[OFF] タイマーをストップ

という処理を記述した。

ついでに setOnLongClickListener で、トグルボタンをロングクリックした時にタイマーをリセットするように設定した。
ロングクリックでリセットする際には、トグルボタンをOFFに変更する。そうしないと、タイマーはリセットされて動いていないのに、トグルがONのままという状況が発生してしまう。

以下、ソースコード全体

MainActivity.java
import android.os.CountDownTimer;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.ToggleButton;

public class MainActivity extends ActionBarActivity {

    static MyCountDownTimer myCountDownTimer;

    // CountDownTimerクラスを継承して、MyCountDownTimerを定義
    class MyCountDownTimer extends CountDownTimer {

        TextView textView = (TextView)findViewById(R.id.textView);
        ToggleButton toggleButton = (ToggleButton)findViewById(R.id.toggleButton);

        public MyCountDownTimer(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }

        // カウントダウン処理
        @Override
        public void onTick(long millisUntilFinished) {
            textView.setText(String.valueOf(millisUntilFinished / 1000)); // ミリ秒 → 秒に変換して)残り時間を表示
        }

        // カウントダウン終了後の処理
        @Override
        public void onFinish() {
            toggleButton.setChecked(false); // toggleボタンをオフにする
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // トグルボタンをタップした時の処理
        ((ToggleButton)findViewById(R.id.toggleButton)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                // トグルON
                if (isChecked) {
                    String time = ((TextView) findViewById(R.id.textView)).getText().toString(); // テキストビューの文字列を取得して、timeに格納
                    myCountDownTimer = new MyCountDownTimer(Integer.parseInt(time) * 1000, 100); // timeをint型に変換した後、ミリ秒に変換。その時間をセットしたmyCountDownTimerをインスタンス化
                    myCountDownTimer.start(); // タイマーをスタート

                    // トグルOFF
                } else {
                    myCountDownTimer.cancel(); // タイマーをストップ
                }
            }
        });

        // トグルボタンをロングタップした時の処理
        ((ToggleButton)findViewById(R.id.toggleButton)).setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                myCountDownTimer.cancel(); // タイマーをストップ
                ((TextView)findViewById(R.id.textView)).setText("15"); // テキストビューに初期値をセット
                ((ToggleButton)findViewById(R.id.toggleButton)).setChecked(false); // toggleボタンをオフにする
                return true;
            }
        });
    }
}

activity_main.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="15"
        android:id="@+id/textView"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New ToggleButton"
        android:id="@+id/toggleButton"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

如何でしょうか。


(関連)
2015/3/10 【Android】【CountDownTimer】 0秒まで表示する (インターバルの挙動を調べてみる)
2015/3/10 【Android】【CountDownTimer】 一時停止の仕組みを改善する その①
2015/3/11 【Android】【CountDownTimer】 一時停止の仕組みを改善する その②

2015年2月28日土曜日

【SyntaxHighlighter】ブログ記事中のコードを分かり易く表示する

ブログを書く際、コードや設定ファイルを記載する事があるが、本文と同様の書式で書いているので、イマイチ見栄えもしないし、読み難い。

他のブログ等ではカッコよく、分かり易く表示されているので、自分も見習ってみたいと思った。
ちょいと調べてみたところ、Syntax Highlighter というのを使うのが良さそうだ。

導入に当たってはこちらの記事(Blogger に Syntax Highlighter を導入する)を大いに参考にさせて頂いた。

一点、若干つまずいたところは、Bloggerのプレビュー画面では、表示が反映されないというところだ。

公開状態にすれば、問題なく表示されるようだ。

試しに、昨日ブログに掲載したコード(+α)を再掲してみる。


[ボタンをタップする毎に、画面の数字を0から1つずつ増やしていくプログラム]

MainActivity.java
  public class MainActivity extends ActionBarActivity {

      private static int i = 0;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);

          final TextView textView = (TextView) findViewById(R.id.textView);
          final Button button = (Button) findViewById(R.id.button);

          // ボタンをクリックした際の処理
          button.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  i++; // i を1増やす
                  textView.setText(String.valueOf(i)); // i の値を表示する
              }
          });
      }
  }

activity_main.xml
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="0"
        android:id="@+id/textView"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Plus Button"
        android:id="@+id/button"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true" />
  </RelativeLayout>

2015年2月27日金曜日

【Android】【Java】 setText() に数値を渡す

Android Studio アプリ開発備忘録


第2回 『 setText() に数値を渡す方法』


setText() は、テキストビューに任意の文字列をセットできるメソッドである。

 TextView.setText("任意の文字列")

と、いう具合に使う。

ある時、プログラムの中で使用されている変数 int i の値を表示したくなった。

TextView.setText(int i) 

とすればエラーになるし、

TextView.setText("i")

とすれば当然画面には 『i』 という文字が表示されてしまう。

これを解決するには String.valueOf() を使えば良いようだ。

 TextView.setText(String.valueOf(i))

これで、変数 i の値を表示できる。

試しに、ボタンをタップする毎に、画面の数字を0から1つずつ増やしていくプログラムを作ってみた。

(※)別途、XMLでボタンとテキストビューを用意しておく必要がある。
===================================================================
public class MainActivity extends Activity {

private static int i = 0;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
   
        final TextView textView = (TextView) findViewById(R.id.textview);
        final Button button = (Button) findViewById(R.id.button);

        // ボタンをクリックした際の処理
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                i++; // i を1増やす
                TextView.setText(String.valueOf(i)); // i の値を表示する
            }
        });
===================================================================

こちらのサイトによると、変換する方法は、この方法を含めて3つあるようだ。