「Linux」カテゴリーアーカイブ

SimpleSAMLphpでShibbolethとSAML / シングルサインオン

SimpleSAMLphpを使ってSPとテスト用IdP(Shibboleth)を構築する。
Shibbolethによる学術認証フェデレーションへの要参加するシステム構築で、SAMLでシングルサインオンをする必要が出てきました。

検証用にIdentity Providers(IdP)を用意する必要があり、OpenAMとかいろいろ漁ったところ、サクッと検証サーバ立てるのに、SimpleSAMLphpが一番手っ取り早かった。spもSimpleSAMLphpを利用します。

以下、インストール設定メモです。
idp,spとも同一サーバ上で動作させることができますが、分かりやすいようにサーバを分けました。
(※一度インストール~動作まで理解できれば同一サーバでも導入はすごく簡単です。)
・apache,phpは導入済みを前提とします。
・ホストのFQDNが正しく表示されること。

【確認環境】
—————————————————-
1.iDP (ユーザ認証サーバ)
FQDN:idp.sco.jp
https://idp.sco.jp
CentOS 7.2.1511
apache 2.4.6
php 5.4
simplesamlphp-1.14.9

—————————————————-
2.SP (ユーザ認証要求)
FQDN:sp.starserv.ne.jp
http://sp.starserv.ne.jp
RedHat 6.8
httpd-2.2
php 5.6
simplesamlphp-1.14.9
—————————————————-
【目標】
http://sp.starserv.ne.jpからSAMLでシングルサインオンできること。

【インストール】
1.iDP

https://simplesamlphp.org/docs/stable/simplesamlphp-install
を参考にします。

simplesamlphp-1.14.9.tar.gzをダウンロードし/var/simplesamlphp に展開する。
Apacheの設定で
Alias /simplesaml /var/simplesamlphp/www
を追加する。
今回は、httpsで動かします。(httpでも問題ありません。)

https://idp.sco.jp/simplesaml/
1

■iDPの設定
/var/simplesamlphp/config/config.php

・admin ユーザのログインパスワード123から変更

'auth.adminpassword' => '1234567890',

※パスワードは暗号化できますが、とりあえずplainで。
・管理者(admin)でログインできるか確認。

・idpモジュールの有効化

'enable.saml20-idp' => true,
'enable.shib13-idp' => true,

※enable.saml20-idpは今回使わないので、falseでいいですが実験のためこれも有効にしました。
SimpleSAMLphp設定ページで有効になっているか確認します。
2
・認証方法を設定
ユーザ属性データは、exampleauthを使うことにし、
authsources.phpに記載します。(LDAPとかいろんなDBが利用できますがここはパス)
まずは有効化設定(enableファイルを作成する。)
touch /var/simplesamlphp/modules/exampleauth/enable

ID=user1 password=test1
ID=user2 password=test2
の2ユーザ作成
/var/simplesamlphp/config/authsources.php

    'example-userpass' => array(
        'exampleauth:UserPass',
        'user1:test1' => array(
            'uid' => array('user1'),
            'eduPersonAffiliation' => array('member', 'developer'),
        ),
        'user2:test2' => array(
            'uid' => array('user2'),
            'eduPersonAffiliation' => array('member', 'admin'),
        ),
    ),

・自己証明書を作成
/var/simplesamlphp/certフォルダーに、server.crt、server.pemを作成します。
cd /var/simplesamlphp/cert
openssl req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out server.crt -keyout server.crt

/var/simplesamlphp/metadata/saml20-idp-hosted.php

        'privatekey' => 'server.pem',
        'certificate' => 'server.crt',

/var/simplesamlphp/metadata/shib13-idp-hosted.php

        'privatekey' => 'server.pem',
        'certificate' => 'server.crt',

iDPの設定は一旦ここまで、後で「連携」でメタデータをSPと相互に信頼設定する必要があります。

■SPの設定
iDPの設定と同じように、simplesamlを設置します。
http://sp.starserv.ne.jp/simplesaml
iDPは動作させません。SPとして使います。
/var/simplesamlphp/config/config.php

'auth.adminpassword' => 'qwertyuiop',

※パスワードは暗号化できますが、とりあえずplainで。
・管理者(admin)でログインできるか確認。

・自己証明書を作成
/var/simplesamlphp/certフォルダーに、server.crt、server.pemを作成します。
・動作確認のため、デバッグモードにする。

'loggingdir' => '/var/log/simplesaml/',
'debug' => true,
'showerrors' => true,
'errorreporting' => true,
'logging.level' => LOG_DEBUG,
'logging.handler' => 'file',

・iDPの指定
/var/simplesamlphp/config/authsources.php

    'default-sp' => array(
        'saml:SP',
.
.
.
        'idp' => 'https://idp.sco.jp/simplesaml/shib13/idp/metadata.php',

idp’がNULLの場合はログイン画面の前にiDP選択画面が出てくるので、明示的に指定する。

・メタデータの設定
■iDP側 https://idp.sco.jp/simplesaml
SP側のSimpleSAMLphp設定ページ‐「連携」で、
default-sp「メタデータを表示」をクリックして、表示されたメタデータXMLをクリップボードにコピーする。
3

4

iDP側のSimpleSAMLphp設定ページ‐「連携」-XML を 「SimpleSAMLphpメタデータに変換」を
クリックし、貼り付けて「パース」する。
php用にコンバート表示されます。
shib13-sp-remote、saml20-sp-remoteの2つ。

5

表示された内容の、$metadata以下を
/var/simplesamlphp/metadata/saml20-sp-remote.php
/var/simplesamlphp/metadata/shib13-sp-remote.php
に記載します。上手くいけば、下記のようにSPが連携登録されます。
5-1

■SP側 http://sp.starserv.ne.jp/simplesaml
iDP側のSimpleSAMLphp設定ページ‐「連携」で、
SAML 2.0 IdPメタデータ「メタデータを表示」をクリックして、表示されたメタデータXMLをクリップボードにコピーする。
SP側のSimpleSAMLphp設定ページ‐「連携」-XML を 「SimpleSAMLphpメタデータに変換」をクリックし、貼り付けて「パース」する。
php用にコンバート表示されます。
saml20-idp-remoteの1つ。
表示された内容の、$metadata以下を
/var/simplesamlphp/metadata/saml20-idp-remote.php
に記載します。
同じ作業を
Shib 1.3 IdPメタデータについて行う。
/var/simplesamlphp/metadata/shib13-idp-remote.php

6

●連携の確認1

SP側 http://sp.starserv.ne.jp/simplesaml – 「認証」タブ
「設定されている認証元をテスト」
default-spをクリック

7
ログイン画面が表示されます。
8
user1:test1
user2:test2
でログインできればOK

9

●連携の確認2
最後にテストアプリからログインを試します。これができれば目的達成です。
サンプルプログラム
index.php

<?
require_once('/var/simplesamlphp/lib/_autoload.php');

$as = new SimpleSAML_Auth_Simple('default-sp');
$as->requireAuth();
$attributes = $as->getAttributes();

?>

<div style="font-weight: bold;">Hello, SimpleSAMLphp</div>
<table border="1">
<?php  foreach ($attributes as $key => $value): ?>
  <tr>
    <td><?=$key;?></td>
    <td><?=$value[0];?></td>
    <td><?=$value[1];?></td>
  </tr>
<?php endforeach;?>
</table>

<a href="logout.php">logout</a>

logout.php

<?
require_once('/var/simplesamlphp/lib/_autoload.php');

$as = new SimpleSAML_Auth_Simple('default-sp');
$as->requireAuth();
$attributes = $as->getAttributes();

$as->logout('http://sp.starserv.ne.jp/logout.html');
?>

logout.html

<html>
<body>
END
</body>
</html>

http://sp.starserv.ne.jp/index.php アクセス9.5

自動的にログイン認証画面が出ます。(実際ここは、学術認証IDP画面だったり、それぞれ接続先のログイン画面となります。)

10

—動作ログ—
/var/log/simplesaml/simplesamlphp.log
Nov 30 18:43:51 simplesamlphp DEBUG [f138141d33] Session: doLogin(“default-sp”)
Nov 30 18:43:51 simplesamlphp DEBUG [f138141d33] Received SAML1 response
Nov 30 18:43:51 simplesamlphp DEBUG [f138141d33] Loading state: ‘_34d95aa9e4b6b8c2488713e94cd808da3658b6fb79:http://sp.starserv.ne.jp/simplesaml/module.php/core/as_login.php?AuthId=default-sp&ReturnTo=http%3A%2F%2Fsp.starserv.ne.jp%2F’
Nov 30 18:45:20 simplesamlphp DEBUG [f138141d33] Session: Valid session found with ‘default-sp’.
Nov 30 18:45:20 simplesamlphp DEBUG [f138141d33] Session: Valid session found with ‘default-sp’.
Nov 30 18:45:20 simplesamlphp DEBUG [f138141d33] Session: doLogout(‘default-sp’)
Nov 30 18:45:20 simplesamlphp DEBUG [f138141d33] Session: ‘default-sp’ not valid because we are not authenticated.

PHP 4G 大容量のファイルアップロード: enable_post_data_reading

PHPで大容量のダウンロードはサーバ側でfreadで数Kバイト単位で送出すればいいだけですが、アップロードは、そうはいかず、enable_post_data_readingをoff設定にして、自力でpostデータをパースし、データ部を数Kバイト単位で読むしかないのです。実装しました。
(1Gのファイルのダウンロード、アップロードは、オンメモリーで処理すると、
例えば同時接続10でアップロードすると10Gにもなって破綻します。)

[設定]

プログラムはアップロード専用フォルダーにして、
.htaccess

php_flag enable_post_data_reading Off

・enable_post_data_reading Offにすると、$_POST,$_FILEは使えません。$_GET,$SESSIONは有効。
・ini_set()ではenable_post_data_readingは設定できません。

test.html

<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<link rel="stylesheet" href="/css/jquery-ui.css" type="text/css" />
<script type="text/javascript" src="/js/jquery.min.js"></script>
<title></title>
</head>

<SCRIPT Language="Javascript1.2">
<!--

$(function() {
  $('input[type=file]').change(function() {
        file1 = document.getElementById("userfile1").files[0];
        file2 = document.getElementById("userfile2").files[0];
        file3 = document.getElementById("userfile3").files[0];

        if(typeof file1 === "undefined"){
                document.sedesupload.up_size1.value = "0\t";
        }else{
                document.sedesupload.up_size1.value = file1.size + "\t" + file1.name;
        }

        if(typeof file2 === "undefined"){
                document.sedesupload.up_size2.value = "0\t";
        }else{
                document.sedesupload.up_size2.value = file2.size + "\t" + file2.name;
        }

        if(typeof file3 === "undefined"){
                document.sedesupload.up_size3.value = "0\t";
        }else{
                document.sedesupload.up_size3.value = file3.size + "\t" + file3.name;
        }

  });
});

-->
</SCRIPT>

<body>
<br>
<h1>TEST</h1>

<form enctype="multipart/form-data" name="sedesupload" action="binupload-multi.php" method="POST">
<input type="hidden" name="trkno" value="DD00008">
<input type="hidden" name="user" value="T">

<input type="hidden" name="up_size1" id="up_size1" value="0">
<input type="hidden" name="up_size2" id="up_size2" value="0">
<input type="hidden" name="up_size3" id="up_size3" value="0">

No1: <input name="userfile1" id="userfile1" type="file" /><br>
No2: <input name="userfile2" id="userfile2" type="file" /><br>
No3: <input name="userfile3" id="userfile3" type="file" /><br>

<br>
<input type="submit" value="ファイルを送信"/>
</form>
</html>

・最大3ファイルを選択
・POSTデータに3ファイルのファイルサイズ、ファイル名を同時にセット。ファイルサイズをサーバ側に知らせないと、受け側のプログラムが面倒な処理になるので、入れてます。

binupload-multi.php

<?php
# php 5.4 or higher
#.htaccess php_flag enable_post_data_reading Off
#
#print_r($_SESSION);    // is
#print_r($_REQUEST);    // is
#print_r($_GET);        // is
#print_r($_POST);       // null

//-- config base -----------------
ini_set("max_execution_time",180 * 3);
$base_dir = "/home/temp";
$READ_BUF_SIZE = 8192;
//-------------------------------

$handle_in = fopen("php://input", "rb");
if (FALSE === $handle_in) {
    exit("Failed to open stream to URL");
}

$header_sec = 0;

$contents = "";
$multi_part ="";
$multi_part_now = "";

while (!feof($handle_in)) {
        // ------------------------------------
        // STEP 0: first GET multi part strings
        // ------------------------------------
        if($header_sec == 0){
                //get multipart string
                $multi_part = str_replace("\r\n", '', fgets($handle_in));
                $header_sec = 1; //Next Content
                if(strlen($multi_part) == 0){
                        print "multi_part_len 0 error \r\n";
                        exit;
                }
                print "[". $multi_part . "]<br><br>";

                continue;
        }

        // ------------------------------------
        // STEP 2: next GET multi part strings & check
        // ------------------------------------
        if($header_sec == 2){
                $multi_part_now = str_replace("\r\n", '', fgets($handle_in));

                if($multi_part_now === $multi_part){
                        $header_sec = 1;
                        continue;
                }else{
                        // Last part check
                        if($multi_part_now == $multi_part . "--"){
                                print "sucess\r\n";
                        }else{
                                print "sequence error step 2\r\n";
                        }
                        break;
                }
        }

        // --------------------------------------------------
        // STEP 1: GET Content-Disposition (post name & data)
        // --------------------------------------------------
        if($header_sec == 1){
                $line = str_replace("\r\n", '', fgets($handle_in));
                $sbuf = explode('filename="',$line);
                if(count($sbuf) == 1){
                        //parameter
                        $pbuf = explode('name="',$sbuf[0]);
                        $pname = mb_substr($pbuf[1], 0, -1);

                        fgets($handle_in); //null line;
                        $pdata = str_replace("\r\n", '', fgets($handle_in));
                        print "NAME=[".$pname . "] DATA=[" . $pdata . "]<br>";

                        if($pname === "up_size1"){
                                $upbuf = explode("\t",$pdata);
                                $upfile_size[0] = $upbuf[0];
                                $upfile_name[0] = $upbuf[1];
                        }elseif($pname === "up_size2"){
                                $upbuf = explode("\t",$pdata);
                                $upfile_size[1] = $upbuf[0];
                                $upfile_name[1] = $upbuf[1];
                        }elseif($pname === "up_size3"){
                                $upbuf = explode("\t",$pdata);
                                $upfile_size[2] = $upbuf[0];
                                $upfile_name[2] = $upbuf[1];
                        }
                        $header_sec = 2;
                        continue;

                }else{
                        //file parameter
                        $filename = mb_substr($sbuf[1], 0, -1);

                        fgets($handle_in); //skip Content-Type:;
                        fgets($handle_in); //skip null line;
                        print "FLENMAE=[".$filename . "]<br>";

                        if($filename == ""){
                                fgets($handle_in); //skip null line;
                                $header_sec = 2;
                                continue;
                        }else{
                                for($i = 0 ; $i < 3 ; $i++){
                                        if($filename === $upfile_name[$i]){
                                                $filesize = $upfile_size[$i];
                                                $handle_out = fopen($base_dir . "/" . $filename, "w+b");
                                                $header_sec = 3;
                                                break;
                                        }
                                }
                        }
                }
        }

        // --------------------------------------------------
        // STEP 3: GET file binary contests
        // --------------------------------------------------
        if($header_sec == 3){
                if($filesize < $READ_BUF_SIZE){
                        $contents = fread($handle_in, $filesize);
                        fwrite($handle_out,$contents);
                }else{
                        $readsize = 0;
                        while (!feof($handle_in)) {
                                $contents = fread($handle_in, $READ_BUF_SIZE);

                                $readsize += $READ_BUF_SIZE;
                                fwrite($handle_out,$contents);

                                if(($readsize) + $READ_BUF_SIZE >= $filesize){
                                        $contents = fread($handle_in, $filesize - $readsize);
                                        fwrite($handle_out,$contents);
                                        break;
                                }
                                //usleep(1000);
                        }

                }
                fgets($handle_in); //null line;
                $header_sec = 2;
                fclose($handle_out);
        }
}
fclose($handle_in);

?>

・max_execution_timeは、540秒(9分)
・マルチパートを区切りに、POSTデータをパース
・ファイルの場合は、ファイルサイズを基に8192バイト単位で受信しファイルに書き込み。

※テストコードなのでコードレビューはしておりません。
プログラム中に固定化(3ファイル前提)されているところもありますので利用される方はご注意を。日本語ファイル名等はそれなりの処理をして下さい。

112

113

合計9.5G アップロードできました。
この時、httpdのCPU使用率は50%でした。
※usleep(1000) 1msecのsleepを入れるとCPU使用率は13%になります。当然アップロード時間も増加しますので、max_execution_timeは要調整。

httpd CPU使用率
———————-
50% なし
34% usleep(100)
28% usleep(300)
13% usleep(1000) 1msec
———————–

まあ、昔のperlのcgi時代は全て自分でPOSTデータはパースしてたので、
全然違和感はないですが、今は全部やってくれるので知らない内に
オンメモリーで処理されていること忘れがちあるいはそもそも、そんな考えも
しないという新人の技術者も多いかと思いますのでご注意を。

【Asai’s memo】PCセットアップ編(入力履歴の削除)

夏風邪が流行っております。
皆様、体調にはお気を付け下さいね。

最新、PCのセットアップを行う機会が増えました。

第三者にPCを利用してもらうときに、
セットアップ時に入力していた情報が消えないまま勝手に保存されていた。なんてことがあります。
削除方法をメモとして記載しておきますので参考になればと。

入力した履歴を消す設定(windows7)

①スタートメニューより[コントロールパネル]を開く。
②デスクトップのカスタマイズをクリックする。
 20150630_1
③タスクバーと[スタート]メニューをクリックする。
 20150630_2
④[スタート]メニュータブを選択し、プライバシー欄の最近開いたプログラムを[スタート]メニューに保存し表示する(P)のチェックを外す。
 20150630_3
④適用ボタンを押して完了。

上記を行うと、勝手に保存されるスタートメニューで入力した情報を削除してくれますよー。

Web APIをShellで使おう!(メールで情報を送る)2

こんにちは。Arakawaです。
さて、二回目ですが、取得したデータを定期的にメールで送ることをしたいと思います。
とはいえ、昨今のスマートフォンは、指先一つで天気予報を見ることができ、わざわざ
メールで送信する意味ってどこにあるのか・・・。いや、きっとどこかで役に立つような・・。

small_232678770
photo credit: chrn via photopin cc

1.先ずは復習。データが取得できるか、試してみましょう。

# wget -q -O - http://weather.livedoor.com/forecast/webservice/json/v1?city=260010 | jq '.forecasts[]|.date,.telop,"最高気温->",.temperature.max.celsius,"最低気温->",.temperature.min.celsius'
"2014-12-12"
"曇時々晴"
"最高気温->"
"12"
"最低気温->"
null
"2014-12-13"
"晴時々曇"
"最高気温->"
"8"
"最低気温->"
"2"
"2014-12-14"
"曇時々晴"
"最高気温->"
null
"最低気温->"
null

2.取得できましたね。では、これをメールにしてみましょうか?。

shellのソース(tenki.shとしました。)

#!/bin/bash
export LANG=ja_JP.UTF-8

echo "MIME-Version: 1.0
Content-Type: text/plain; charset="ISO-2022-JP"
Content-Transfer-Encoding: 7bit
X-Priority: 3
Subject:天気予報
From: 送り元@
To: 送り先@

`wget -q -O - http://weather.livedoor.com/forecast/webservice/json/v1?city=260010 | jq '.forecasts[]|.date,.telop,"最高気温->",.temperature.max.celsius,"最低気温->",.temperature.min.celsius'`
" | /usr/bin/nkf -j | /usr/sbin/sendmail -t

exit 0

メールの抜粋

件名:天気予報

本文:
"2014-12-12"
"曇時々晴"
"最高気温->"
"12"
"最低気温->"
null
"2014-12-13"
"晴時々曇"
"最高気温->"
"8"
"最低気温->"
"2"
"2014-12-14"
"曇時々晴"
"最高気温->"
null
"最低気温->"
null

3.あと、定期的に送るため、クーロンに以下のように設定しました。

0 7 * * * /お好きなパス/tenki.sh

としておきましょう。毎朝7時メールが来ます。

以上のようにすれば、(まだ編集の余地は大いにあるのですが。X-( ) メールで定期的に天気予報を受け取ることができます。

簡単ですが、今日はこの辺で・・。

Web APIをShellで使おう!(jqをCentOS6.5にインストール編)1

みなさん、こんにちは。
Arakawaです。最近CUIがシンプルで心地よく感じる時があり、できるならば
CUIで過ごせないかと時々画策しています。
small__5450429373
photo credit: blakespot via photopin cc

さて、CUIで過ごしていると、どうしてもいろんな情報をCUIで取得したくなります。
天気予報であったり、ニュースであったり、技術情報であったり・・。
そーいうのをLynxでみるのも悪くはないですが、もっとシンプルに
取得したい・・・。思いついたのがWeb APIでした。

Web APIは、XMLやJSONで提供されていることが多いと思います。そこでそれらを
簡単に処理できるアプリがないかなぁ~と思いいろいろググっていますと、
jqというアプリが便利だということがわかってきました。

jqを一度インストールしてみて、いろいろ画策(笑)していきたいと思います。

——————–
インストール実施した内容ログ

1.http://stedolan.github.io/jq/download/からバイナリを適当なところにダウンロードします。

#cd /tmp
#wget http://stedolan.github.io/jq/download/linux64/jq

2.実行権限を与えました。755にします。

#chmod 755 jq

3.では、動くかどうか、試してみたいと思います。Web APIでまずテストといえば
天気予報が簡単でよさそうな気がしています。Livedoorさんのサービスをお借りしたいと思います。
http://weather.livedoor.com/weather_hacks/webservice
から京都のデータをゲットしたいともいます。2014/11現在このアドレスでデータが取得できるようです。
http://weather.livedoor.com/forecast/webservice/json/v1?city=260010

では、実際に試してみます。

#wget -q -O - http://weather.livedoor.com/forecast/webservice/json/v1?city=260010 | ./jq '.'

こんな感じで表示されました。

{
  "pinpointLocations": [
    {
      "link": "http://weather.livedoor.com/area/forecast/2610000",
      "name": "京都市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2620400",
      "name": "宇治市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2620600",
      "name": "亀岡市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2620700",
      "name": "城陽市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2620800",
      "name": "向日市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2620900",
      "name": "長岡京市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2621000",
      "name": "八幡市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2621100",
      "name": "京田辺市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2621300",
      "name": "南丹市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2621400",
      "name": "木津川市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2630300",
      "name": "大山崎町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2632200",
      "name": "久御山町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2634300",
      "name": "井手町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2634400",
      "name": "宇治田原町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2636400",
      "name": "笠置町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2636500",
      "name": "和束町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2636600",
      "name": "精華町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2636700",
      "name": "南山城村"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/2640700",
      "name": "京丹波町"
    }
  ],
  "link": "http://weather.livedoor.com/area/forecast/260010",
  "forecasts": [
    {
      "dateLabel": "今日",
      "telop": "晴のち曇",
      "date": "2014-11-12",
      "temperature": {
        "min": null,
        "max": {
          "celsius": "21",
          "fahrenheit": "69.8"
        }
      },
      "image": {
        "width": 50,
        "url": "http://weather.livedoor.com/img/icon/5.gif",
        "title": "晴のち曇",
        "height": 31
      }
    },
    {
      "dateLabel": "明日",
      "telop": "曇時々晴",
      "date": "2014-11-13",
      "temperature": {
        "min": {
          "celsius": "9",
          "fahrenheit": "48.2"
        },
        "max": {
          "celsius": "13",
          "fahrenheit": "55.4"
        }
      },
      "image": {
        "width": 50,
        "url": "http://weather.livedoor.com/img/icon/9.gif",
        "title": "曇時々晴",
        "height": 31
      }
    },
    {
      "dateLabel": "明後日",
      "telop": "曇時々晴",
      "date": "2014-11-14",
      "temperature": {
        "min": null,
        "max": null
      },
      "image": {
        "width": 50,
        "url": "http://weather.livedoor.com/img/icon/9.gif",
        "title": "曇時々晴",
        "height": 31
      }
    }
  ],
  "location": {
    "city": "京都",
    "area": "近畿",
    "prefecture": "京都府"
  },
  "publicTime": "2014-11-12T11:00:00+0900",
  "copyright": {
    "provider": [
      {
        "link": "http://tenki.jp/",
        "name": "日本気象協会"
      }
    ],
    "link": "http://weather.livedoor.com/",
    "title": "(C) LINE Corporation",
    "image": {
      "width": 118,
      "link": "http://weather.livedoor.com/",
      "url": "http://weather.livedoor.com/img/cmn/livedoor.gif",
      "title": "livedoor 天気情報",
      "height": 26
    }
  },
  "title": "京都府 京都 の天気",
  "description": {
    "text": " 近畿地方は、湿った空気の影響で雲が広がり、弱い雨の降っているところ\nがあります。\n\n 今日の京都府は、湿った空気の影響で雲が広がりやすく、午後は日本海か\nら気圧の谷が南下するため、北部では夕方から雨の降るところがあるでしょ\nう。\n\n 明日の京都府は、気圧の谷や寒気の影響で北部を中心に雲が広がり、雨や\n雷雨となるところがある見込みです。",
    "publicTime": "2014-11-12T10:36:00+0900"
  }
}

ちゃんと整形されていますね。めでたしめでたし。
では、せっかくなので、どこからでもアクセスできるようにしておきましょうかね。
4. 設定

#mv jq /usr/local/bin/
#wget -q -O - http://weather.livedoor.com/forecast/webservice/json/v1?city=260010 | jq '.'

皆さん動きましたか?./ がついていないことを確認してくださいね。
動かない人は、どこにパスが通っているか確認してください。

#echo $PATH

では、次は、これらを使ってもう少し、面白そうなことをしていこうと思います。
では。($・・)/~~~