カテゴリー
プログラム 社内SE

sshのconfigのhostをリスト表示し、そのままssh

sshコマンドを打つときに.configに設定したホスト名を忘れて躓く方のための補助用のスクリプトです。
Windows Powershell版Linux bash版それぞれあります。

使い方

コマンド:sshfc [ホスト名の一部]

sshfcとだけ打つとconfigファイルに設定した全ホスト名が表示されます。
sshfc sysのように第一引数をつけて打つとホスト名にsysを含むSSH先が表示されます。続けて番号選択でそのままSSH接続です。
コマンド名は”ssh from config” の頭をとってsshfcとしています。以下使用例になります。

C:\> sshfc sys
1) system
2) test_system
3) dev_system
SSHするホストの番号を入力してください。[c]を押すとキャンセルされます。: 3
ssh dev_system
hogehoge@192.168.1.100's password:
....

Linux Bash版 インストール方法

以下の要領で、githubからスクリプトをダウンロードしてPATHが通っているフォルダに置いてください

git clone https://github.com/y-toy/linux_sshfc.git
chmod +x ./linux_sshfc/sshfc
# 以下環境に合わせて実行ください。
mv ./linux_sshfc/sshfc /usr/local/bin/

Windows Powershell版 インストール

以下の要領で、”ユーザフォルダ\bin”をPATHに追加して、githubからファイルをダウンロードします。

New-Item ($HOME + "\bin") -ItemType Directory
[System.Environment]::SetEnvironmentVariable("Path", $env:Path + ";%USERPROFILE%\bin", "User")

git clone https://github.com/y-toy/windows_sshfc.git
cd windows_sshfc
Move-Item ./sshfc.ps1 ($HOME + "\bin")

スクリプト自体が実行できない場合は以下コマンドを実行してみてください。スクリプトの実行制限を緩めます。

Set-ExecutionPolicy Unrestricted -scope CurrentUser

上記は確実ですが、上記をやらなくても、githubからダウンロードしたファイルを、空行を入れるなど意味のない編集をメモ帳などでして、BOM付UTF-8で保存すると実行できるようになるはずです。(普通のUTF-8はNGです。メモ帳の[名前を付けて保存]で UTF-8 BOM付きを選択。)

雑記

最近、WindowsのsshクライアントをMicrosoft謹製の「Windows terminal」に変更しました。 「Windows terminal」 自体は秀逸なのですが、以前使用していたsshクライアントと比べ若干使いずらいのが、SSHの接続先の選択方法。なのでツールを作ってカバーした感じです。

作ったツールが思いのほか便利だったので、bash版も作成しサーバに入った後のSSHも同様にできるようにしました。「.configに設定したホスト名なんだっけ?」と考えることが無くなりました。

Powershell のスクリプトを書くのは初めて。こういった作業効率化のちょっとしたコードを書く分には構文に慣れている分、過去のvbscriptで良いかなあという印象です。bashのスクリプトも簡単な数行ものを除けば、めったに書かないのでいつも構文忘れます。

Powershell版は日英つけていますがps1ファイルは配布が難しいので意味ないです。Bash版は面倒なので英語だけです。

カテゴリー
プログラム 社内SE

PHP ヤマト配送状況取得 2021年サイト変更対応

2021年9月 クロネコヤマトの荷物問い合わせシステムのページレイアウトが変更になって、送り状番号から荷物の配送状況を取得していたバッチがエラーとなった。
APIを使用すればよいという話だが、ヤマトビジネスメンバーズは他部署で契約&契約した人は退職で詳細不明になっている。まあ、面倒なので、PHPで問い合わせページから荷物状況を取得するプログラムを書いた。

実際はベタ書きしているが、かっこ悪いので、公開に当たりクラス化。1回テスト確認はしたが、使っていないので、エラーがああるかも。PHP5.4だが、7でも8でも動くと思う。取得した状況には「▶」が付いているので不要であれば消して使ってください。
内部でサイト解析のためDOMDocument使っているので使える環境のこと。以下、使い方とクラス。

使い方

$truckNumbers = array(
'444486855064',
'4444-9099-9090',
'444490999101',
); // いくつでも可

$objYamato = new clsGetYamatoDeliverStatus();
$aryDelivStatus = $objYamato->getYamatoDeliverStatus($truckNumbers);
if ($aryDelivStatus === false){ /* エラー ヤマト追跡番号 状況取得失敗 */ }

var_dump($aryDelivStatus);
/*
結果は[
(‐無しの追跡番号),
(‐有りの追跡番号),
状況日,
配達状況,
問い合わせ時間]の配列
array(3) {
  [0] =>
  array(5) {
    [0] =>
    string(12) "444486855064"
    [1] =>
    string(14) "4444-8685-5064"
    [2] =>
    string(5) "10/20"
    [3] =>
    string(16) "▶ 配達完了"
    [4] =>
    string(19) "2021/10/12 09:59:10"
  }
  [1] =>
  array(5) {
    [0] =>
    string(12) "444490999090"
    [1] =>
    string(14) "4444-9099-9090"
    [2] =>
    string(5) "10/20"
    [3] =>
    string(16) "▶ 配達完了"
    [4] =>
    string(19) "2021/10/12 09:59:10"
  }
  [2] =>
  array(5) {
    [0] =>
    string(12) "444490999101"
    [1] =>
    string(14) "4444-9099-9101"
    [2] =>
    string(5) "10/20"
    [3] =>
    string(16) "▶ 配達完了"
    [4] =>
    string(19) "2021/10/12 09:59:10"
  }
}
*/

荷物状況取得 クラス

<?php

class clsGetYamatoDeliverStatus{

	// 配列$truckNumbersに指定された追跡番号の状況を全て取得する。$truckNumbersは‐有でも無しでも可。
	// 戻り値は次の配列の配列 ["123456781234"(‐無しの追跡番号),"1234-5678-1234"(‐有りの追跡番号),mm/dd 状況日,配達状況,Y/m/d H:i:s 問い合わせ時間]
	// 戻り値がfalseの場合、エラー(ヤマト追跡サイトにアクセス出来ない)
	public function getYamatoDeliverStatus($truckNumbers){
		// 10件ずつヤマトのサイトからトラッキング情報を取得する。
		$numnerCnt = count($truckNumbers);
		$getTruckNumbers = array();
		$aryAllStatusInfo = array();
		for ($i=0;$i < $numnerCnt;$i++){
			$getTruckNumbers[] = $truckNumbers[$i];
			if (count($getTruckNumbers) == 10){
				$aryRetStatus = $this->getYamatoDeliverStatusCore($getTruckNumbers);
				$aryTemp = array_merge($aryAllStatusInfo, $aryRetStatus);
				if ($aryTemp === false){ return false; }
				$aryAllStatusInfo = $aryTemp;
				$getTruckNumbers = array();
			}
		}

		// 残っている場合
		if (count($getTruckNumbers) > 0){
			$aryRetStatus = $this->getYamatoDeliverStatusCore($getTruckNumbers);
			$aryTemp = array_merge($aryAllStatusInfo, $aryRetStatus);
			if ($aryTemp === false){ return false; }
			$aryAllStatusInfo = $aryTemp;
		}

		return $aryAllStatusInfo;
	}

	// ヤマトの追跡サイトにアクセスし、10件づつ結果を取得する。
	// 引数の$truckNumbersは配列で10件まで。
	// 戻り値は次の配列の配列 ["123456781234"(‐無しの追跡番号),"1234-5678-1234"(‐有りの追跡番号),mm/dd 状況日,配達状況,Y/m/d H:i:s 問い合わせ時間]
	// 戻り値がfalseの場合エラー(サイトにアクセス出来ない場合のみ)
	private function getYamatoDeliverStatusCore($truckNumbers){
		$aryRet = array();

		$url = 'https://toi.kuronekoyamato.co.jp/cgi-bin/tneko';
		$tnumCnt = count($truckNumbers);

		$aryPOST = array(
			'mypagesession' => '',
			'backaddress' => '',
			'backrequest' => '',
			'number00' => 2
		);
		$j=0;
		for ($i=1;$i<=10;$i++){
			$postName = 'number';
			if ($i < 10){
				$postName .= '0' . $i;
			}else{
				$postName .= $i;
			}
			if ($j < $tnumCnt){
				$aryPOST[$postName] = $truckNumbers[$j];
			}else{
				$aryPOST[$postName] = '';
			}
			$j++;
		}

		$http_build = http_build_query($aryPOST,'','&');
		$header = array(
			"Content-Type: application/x-www-form-urlencoded",
			"Content-Length: ".strlen($http_build)
		);
		// オプション
		$opts = array("http" => array(
			"method" => "POST",
			"header"  => implode("\r\n", $header),
			"content" => $http_build)
		);
		$scc = stream_context_create($opts);

		// 読み取り
		$get_html = file_get_contents($url, false, $scc);
		if ($get_html === false){ return false; }

		// staus取得時間
		$getTime = Date('Y/m/d H:i:s');

		$dom = new DOMDocument();
		$ret = $dom->loadHTML($get_html);

		$divResult = $this->getElementsByClassName($dom, 'tracking-box-area', 'div');
		$divLen = count($divResult);
		for($i=0;$i < $divLen;$i++){
			// このdivの下のinputのvalueを取得
			$domInputs = $this->getDomNodeFromTagName($divResult[$i], 'input');
			if (count($domInputs) == 0){ continue; }
			$trNum = $domInputs[0]->attributes->getNamedItem('value')->nodeValue;
			if ($trNum == ''){ continue; }

			// このdiv以下の日付を取得
			$date = '';
			$dateNodes = $this->getDomNodeFromClassName($divResult[$i], 'data date');
			if (count($dateNodes) == 0){ continue; }
			$date = $dateNodes[0]->nodeValue;

			// このdiv以下の状況を取得
			$state = '';
			$dateStates = $this->getDomNodeFromClassName($divResult[$i], 'data state');
			if (count($dateStates) == 0){ continue; }
			$state = $dateStates[0]->nodeValue;

			$aryRet[] = array(
				str_replace('-','',$trNum), // ‐なし
				$trNum, // ‐あり
				$date, // 状況になった日付
				$state, // 状況
				$getTime // 取得時刻
			);

		}

		return $aryRet;
	}

	private function getElementsByClassName($dom, $ClassName, $tagName=null) {
		if($tagName){
			$Elements = $dom->getElementsByTagName($tagName);
		}else {
			$Elements = $dom->getElementsByTagName("*");
		}
		$Matched = array();
		for($i=0;$i<$Elements->length;$i++) {
			if($Elements->item($i)->attributes->getNamedItem('class')){
				if(strpos($Elements->item($i)->attributes->getNamedItem('class')->nodeValue, $ClassName) !== false) {
					$Matched[]=$Elements->item($i);
				}
			}
		}
		return $Matched;
	}

	// 子ノード以下に指定のタグ名を持つdomNodeの一覧を返す。
	private function getDomNodeFromTagName($domNode, $tagName){
		$aryRet = array();
		foreach ( $domNode->childNodes as $chiledNode ){
			if ($chiledNode->nodeName == $tagName){
				$aryRet[] = $chiledNode;
			}
			if ($chiledNode->hasChildNodes()){
				$aryTemp = $this->getDomNodeFromTagName($chiledNode, $tagName);
				foreach($aryTemp as $temp){
					$aryRet[] = $temp;
				}
			}
		}
		return $aryRet;
	}

	// 子ノード以下に指定のクラス名を持つdomNodeの一覧を返す。
	private function getDomNodeFromClassName($domNode, $className){
		$aryRet = array();
		foreach ( $domNode->childNodes as $chiledNode ){
			if ($chiledNode->hasAttributes()){
				$nodAttr = $chiledNode->attributes->getNamedItem('class');
				if ($nodAttr != null){
					$nodeClasses = $nodAttr->nodeValue;
					if (strpos($nodeClasses, $className) !== false){
						$aryRet[] = $chiledNode;
					}
				}
			}
			if ($chiledNode->hasChildNodes()){
				$aryTemp = $this->getDomNodeFromClassName($chiledNode, $className);
				foreach($aryTemp as $temp){
					$aryRet[] = $temp;
				}
			}
		}
		return $aryRet;
	}
}
カテゴリー
プログラム 社内SE

Pear Mailで送信したメールを 送信済みフォルダに設置する。

PHPのPear::Mailでシステムからメールを送信し、送信したメールを送信済みフォルダに入れたい事案があった。
解決方法がググっても出てこなかったので、自己解決。

基本的には送信したデータをIMAP関数で送信済みのフォルダに設置するだけ。

以下ソース。

include_once("Mail.php");
include_once("Mail/mime.php");

mb_language("uni");
mb_internal_encoding("UTF-8");

// 宛先とヘッダーとボディを作る。省略。以下mimeの例
$mime = new Mail_mime();
$mime->setHTMLBody('something<br />anything');
$body = $mime->get(array(
  'text_encoding' => '7bit',
  'text_charset' => 'UTF-8',
  'html_charset' => 'UTF-8',
  'head_charset' => 'UTF-8'));
$headers = $mime->headers(array(/*省略*/));
$recipients = array(/*省略*/);

// メール送信
$mailObject = Mail::factory("smtp", Array(/*省略*/)); 
$send = $mailObject->send($recipients, $headers, $body);
if (PEAR::isError($send)){ return 'Mail send error! ' . $send->getMessage(); }

////////////////////////////////////////////////////////////////
// IMAPフォルダに設定。

// 送信済みフォルダに設置するデータ作成
$message = '';
foreach($headers as $key => $value){
  $message .= $key . ': ' . $value . "\r\n";
}
$message .= "\r\n" . $body;
// 設置フォルダー
$folderName = '送信済み'; // 自分のIMAPの送信済みフォルダ
$encodeFolder = mb_convert_encoding($folderName, 'UTF7-IMAP', 'UTF-8'); // エンコード
// imap_openの説明を読んで。SSLならポート以降を:993/imap/ssl
$path = "{imap.hogehoge.com:143/imap/notls}INBOX." . $encodeFolder;
$imapStream = imap_open($path,'username@hogehoge.com','password');
if ($imapStream === FALSE){ die(); }
$result = imap_append($imapStream, $path, $message, "\\Seen");
if ($result === FALSE){ die(); }
imap_close($imapStream);

/* end */

以上

カテゴリー
プログラム 社内SE

cordova-plugin-buildinfoのiOSでDebugがfalseになる。

Cordovaのplugin cordova-plugin-buildinfoを利用した際に、iOSでdebugビルドしても”BuildInfo.debug”がfalseになって困っていたところ、以下の方法で解決できたので、備忘録として残しておく。

1)プラグインのバージョンを4.0以上に上げる。
関係ないが、4.0以下だと「このプラグインのせいでiOSのアプリが異常に遅くなることがある」との投稿があったので、とりあえず4.0以上に上げた。

2)XCODEでプロジェクトを開きBuild Settingsから以下2点を変更する。

・Apple LLVM 9.0 – Preprocessing -> Preprocessor Macrosの下にdebugという項目があるので、そこに”DEBUG=1″を追加する。(”Macros”で設定項目を検索するとすぐ見つかる)
・Swift compiler – Custom Flags -> Other Swift Flags
の下にdebugという項目があるので、”-D DEBUG”を追加する。
(”Swift”で設定項目を検索すると見つかる。)

※XCodeプロジェクトの開き方は、Cordova環境直下の./platforms/iosの下に拡張子”.xcworkspace”のファイルがあるのでこれを開く。同じようなファイルで拡張子”.xcodeproj”があるが、こちらではないので注意。

これで、デバッグビルドの場合のみ[BuildInfo.debug]がfalseとなった。

カテゴリー
社内SE 窓口対応

qmail メール送信時エラー( TLS connect failed )までが長い

qmailでメール送信後、数日してから「メールが送信できなかった」とのエラーメールが返ってくるとの相談があった。

エラーメールの内容はだいたい以下のような感じ。

Hi. This is the qmail-send program at xxxxxxxx.
I'm afraid I wasn't able to deliver your message to the following addresses.
This is a permanent error; I've given up. Sorry it didn't work out.

XXXXXXX
TLS connect failed: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1
alert protocol version; connected to xxxxxxxxx
I'm not going to try again; this message has been in the queue too long.

以下のような場合もある。
LS connect failed: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3

どうも相手先のサーバに接続できなかった場合、延々とリトライをしているようだ。
設定を調べてみると、設定ファイル「/var/qmail/control/queuelifetime」に秒数を設定するとその時間以上はリトライをしなくなるらしい。デフォルト値は7日(604800)であった。送れなかったらさっさとエラーを返してほしいが、デフォルト値が長いのにあまり短くするのも怖いので、とりあえず1時間(3600)を設定した。

vi /var/qmail/control/queuelifetime
---
3600
---

ファイルには数字のみ記入する。再起動が必要なので以下をして終了。

qmailctl stop
qmailctl start

qmailctl restartはプロセスが残ってしまうので当方環境ではNGだった。

カテゴリー
社内SE 窓口対応

PDFを印刷した際に一部のページでフォントがおかしくなる

「フォントがおかしい」1年に数回はこの厄介な質問を受けます。今回も、「PDFを印刷したら、XXページ目のフォントだけ違う、何とかしてくれ」という問い合わせが来ました。

該当のPDFを表示した段階では全く違いがわからなかったものの、印刷後の紙で確認すると確かに一部のページで若干文字が濃くなっている感じがします。

PDF自体はシステムから出力していて、特定のページでフォントを変えるような処理はしていませんし、そのシステムを組んでから数年、そんな質問が来たことはありませんでした。

「印刷機の可能性が高く、どうにもなりませんよ。」と回答しかけたところで、思いあたりました。

「PDFを表示するソフトのせいか」

試してみると、Windows 10の旧EdgeでPDFを表示し印刷した時だけ特定のページでフォントがほんの少し濃くなります。Adobe ReaderやChromeで表示、印刷したときは大丈夫なので、旧Edgeが原因なのは間違いなさそうです。

なので「面倒だけど、Edgeで表示したPDFをダウンロードして、それからChromeで表示して印刷してください。」と回答して終わりです。

まあ、面倒だからEdgeで印刷するのでしょうけど。。

カテゴリー
OS 社内SE

FreeBSD : sshやtelnetでログインしたまま、ターミナルを閉じてしまった場合の対処方法

掲題ままですが、残った仮想端末のプロセスを削除する方法を書いておきます。通常、ターミナルを落としたら、プロセスも消えるのでこのような状況にはならないのですが、不安なときの確認にもなるので。。。

  1. ターミナルから切断したユーザでログインし、以下のコマンドを打ち、現在の仮想端末の番号を確認します。
    > who
    以下のような結果が出ます。
    taro  pts/0 6月 16 04:52 (xxxxxx.or.jp)
    taro  pts/1 6月 16 04:56 (xxxxxx.or.jp)
    最も新しくログインしたものが自分なので、ここではpts/1が現在のターミナルです。
     
  2. そのまま以下のコマンドを実行し、仮想端末のプロセスを探します。
    > ps -aux | grep `whoami`
  3. 以下のような結果が表示されます。
    root 27048 0.0 1.4 20428 7976 - Is 04:52 0:00.02 sshd: taro [priv] (sshd) 
    taro 27050 0.0 1.4 20432 8008 - S 04:52 0:00.02 sshd: taro@pts/0 (sshd) 
    root 27124 0.0 1.4 20428 8012 - Is 04:56 0:00.02 sshd: taro [priv] (sshd) 
    taro 27126 0.0 1.4 20432 8036 - I 04:56 0:00.00 sshd: taro@pts/1 (sshd) 
    taro 27051 0.0 0.5 12628 2880 0 Ss 04:52 0:00.01 -sh (sh) 
    taro 27132 0.0 0.4 12324 2636 0 R+ 04:57 0:00.00 ps -aux
    
  4. 古いプロセスをkillします。
    kill 27050