mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-03 16:24:33 +00:00
Latest develop updates is merged with my RaspberryPi Dockerfile version.
Merge branch 'develop' of https://github.com/gogits/gogs into develop
This commit is contained in:
commit
737da1a374
321 files changed
+18787
-44282
No files matched your search
@@ -13,7 +13,7 @@ watch_dirs = [
|
||||
watch_exts = [".go"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["go", "install", "-tags", "sqlite"],# redis memcache cert pam tidb
|
||||
["go", "build", "-tags", "sqlite"],
|
||||
["go", "install", "-race"], # sqlite redis memcache cert pam tidb
|
||||
["go", "build", "-race"],
|
||||
["./gogs", "web"]
|
||||
]
|
||||
@@ -36,3 +36,4 @@ docker/docker/init_gogs.sh
|
||||
gogs.sublime-project
|
||||
gogs.sublime-workspace
|
||||
.tags*
|
||||
release
|
||||
@@ -3,42 +3,43 @@ path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/bradfitz/gomemcache = commit:72a68649ba
|
||||
github.com/Unknwon/cae = commit:2e70a1351b
|
||||
github.com/Unknwon/com = commit:47d7d2b81a
|
||||
github.com/Unknwon/i18n = commit:7457d88830
|
||||
github.com/Unknwon/paginater = commit:cab2d086fa
|
||||
github.com/codegangsta/cli = commit:142e6cd241
|
||||
github.com/go-sql-driver/mysql = commit:527bcd55aa
|
||||
github.com/codegangsta/cli = commit:0302d39
|
||||
github.com/go-macaron/binding = commit:864a5ce
|
||||
github.com/go-macaron/cache = commit:5617353
|
||||
github.com/go-macaron/captcha = commit:875ff77
|
||||
github.com/go-macaron/csrf = commit:3372b25
|
||||
github.com/go-macaron/gzip = commit:4938e9b
|
||||
github.com/go-macaron/i18n = commit:5e728b6
|
||||
github.com/go-macaron/inject = commit:c5ab7bf
|
||||
github.com/go-macaron/session = commit:66031fc
|
||||
github.com/go-macaron/toolbox = commit:ab30a81
|
||||
github.com/go-sql-driver/mysql = commit:d512f20
|
||||
github.com/go-xorm/core = commit:3e10003353
|
||||
github.com/go-xorm/xorm = commit:803f6db50c
|
||||
github.com/go-xorm/xorm = commit:c643188
|
||||
github.com/gogits/chardet = commit:2404f77725
|
||||
github.com/gogits/go-gogs-client = commit:519eee0af0
|
||||
github.com/issue9/identicon =
|
||||
github.com/lib/pq = commit:b269bd035a
|
||||
github.com/go-macaron/binding =
|
||||
github.com/go-macaron/cache =
|
||||
github.com/go-macaron/captcha =
|
||||
github.com/go-macaron/csrf =
|
||||
github.com/go-macaron/gzip =
|
||||
github.com/go-macaron/i18n =
|
||||
github.com/go-macaron/session =
|
||||
github.com/go-macaron/toolbox =
|
||||
github.com/klauspost/compress =
|
||||
github.com/klauspost/crc32 =
|
||||
github.com/klauspost/cpuid =
|
||||
github.com/mattn/go-sqlite3 = commit:b808f01f66
|
||||
github.com/mcuadros/go-version = commit:d52711f8d6
|
||||
github.com/microcosm-cc/bluemonday = commit:85ba47ef2c
|
||||
github.com/mssola/user_agent = commit:a163d6a569
|
||||
github.com/gogits/go-gogs-client = commit:7c02c95
|
||||
github.com/issue9/identicon = commit:5a61672
|
||||
github.com/klauspost/compress = commit:bbfa9dc
|
||||
github.com/klauspost/cpuid = commit:8d9fe96
|
||||
github.com/klauspost/crc32 = commit:3e5c38b
|
||||
github.com/lib/pq = commit:83c4f41
|
||||
github.com/mattn/go-sqlite3 = commit:5651a9d
|
||||
github.com/mcuadros/go-version = commit:d52711f
|
||||
github.com/microcosm-cc/bluemonday = commit:4ac6f27
|
||||
github.com/msteinert/pam = commit:6534f23b39
|
||||
github.com/nfnt/resize = commit:dc93e1b98c
|
||||
github.com/russross/blackfriday = commit:8cec3a854e
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:244f5ac324
|
||||
github.com/russross/blackfriday = commit:300106c
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:10ef21a
|
||||
github.com/Unknwon/cae = commit:7f5e046
|
||||
github.com/Unknwon/com = commit:28b053d
|
||||
github.com/Unknwon/i18n = commit:7457d88830
|
||||
github.com/Unknwon/paginater = commit:7748a72
|
||||
golang.org/x/net =
|
||||
golang.org/x/text =
|
||||
gopkg.in/gomail.v2 = commit:b1e55520bf
|
||||
gopkg.in/macaron.v1 =
|
||||
gopkg.in/ini.v1 = commit:e8c222fea7
|
||||
golang.org/x/text =
|
||||
golang.org/x/crypto =
|
||||
gopkg.in/gomail.v2 = commit:df6fc79
|
||||
gopkg.in/ini.v1 = commit:2e44421
|
||||
gopkg.in/macaron.v1 = commit:1c6dd87
|
||||
gopkg.in/redis.v2 = commit:e617904962
|
||||
|
||||
[res]
|
||||
|
||||
+3
-1
@@ -4,13 +4,15 @@ go:
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y libpam-dev
|
||||
- go get github.com/msteinert/pam
|
||||
|
||||
install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script: go build -v -tags "pam"
|
||||
|
||||
notifications:
|
||||
|
||||
+1
-1
@@ -19,4 +19,4 @@ RUN ./docker/build.sh
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT ["docker/start.sh"]
|
||||
CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
@@ -0,0 +1,35 @@
|
||||
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
|
||||
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildGitHash=$(shell git rev-parse HEAD)"
|
||||
|
||||
TAGS = ""
|
||||
|
||||
RELEASE_ROOT = "release"
|
||||
RELEASE_GOGS = "release/gogs"
|
||||
NOW = $(shell date -u '+%Y%m%d%I%M%S')
|
||||
|
||||
.PHONY: build pack release bindata clean
|
||||
|
||||
build:
|
||||
go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
go build -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
|
||||
govet:
|
||||
go tool vet -composites=false -methods=false -structtags=false .
|
||||
|
||||
pack:
|
||||
rm -rf $(RELEASE_GOGS)
|
||||
mkdir -p $(RELEASE_GOGS)
|
||||
cp -r gogs LICENSE README.md README_ZH.md templates public scripts $(RELEASE_GOGS)
|
||||
rm -rf $(RELEASE_GOGS)/public/config.codekit $(RELEASE_GOGS)/public/less
|
||||
cd $(RELEASE_ROOT) && zip -r gogs.$(NOW).zip "gogs"
|
||||
|
||||
release: build pack
|
||||
|
||||
bindata:
|
||||
go-bindata -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/...
|
||||
|
||||
clean:
|
||||
go clean -i ./...
|
||||
|
||||
clean-mac: clean
|
||||
find . -name ".DS_Store" -print0 | xargs -0 rm
|
||||
@@ -5,23 +5,23 @@ Gogs - Go Git Service [
|
||||
|
||||
##### Current version: 0.6.18 Beta
|
||||
##### Current version: 0.7.20 Beta
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/1.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/2.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/3.png"></td>
|
||||
<td width="33%"><img src="https://gogs.io/img/screenshots/1.png"></td>
|
||||
<td width="33%"><img src="https://gogs.io/img/screenshots/2.png"></td>
|
||||
<td width="33%"><img src="https://gogs.io/img/screenshots/3.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/img/screenshots/4.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/5.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/6.png"></td>
|
||||
<td><img src="https://gogs.io/img/screenshots/4.png"></td>
|
||||
<td><img src="https://gogs.io/img/screenshots/5.png"></td>
|
||||
<td><img src="https://gogs.io/img/screenshots/6.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/img/screenshots/7.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/8.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/9.png"></td>
|
||||
<td><img src="https://gogs.io/img/screenshots/7.png"></td>
|
||||
<td><img src="https://gogs.io/img/screenshots/8.png"></td>
|
||||
<td><img src="https://gogs.io/img/screenshots/9.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -29,20 +29,21 @@ Gogs - Go Git Service [ has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
|
||||
- The demo site [try.gogs.io](https://try.gogs.io) is running under `develop` branch.
|
||||
- :exclamation::exclamation::exclamation:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes and feature Pull Requests, otherwise it's high possibilities that we are not going to merge it.</span>:exclamation::exclamation::exclamation:
|
||||
- :bangbang:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes, otherwise it's high possibilities that we are not going to merge it.</span>:bangbang:
|
||||
- Please [start discussion](http://forum.gogs.io/category/2/general-discussion) or [ask a question](http://forum.gogs.io/category/4/getting-help) on [the forum](http://forum.gogs.io/). GitHub issue tracker only keeps **bugs** and **feature requests**, all other topics will be closed without reason.
|
||||
- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
|
||||
- If you're interested in using APIs, we have experimental support with [documentation](https://github.com/gogits/go-gogs-client/wiki).
|
||||
- If your team/company is using Gogs and would like to put your logo on [our website](http://gogs.io), contact us by any means.
|
||||
|
||||
#### Other language version
|
||||
|
||||
- [简体中文](README_ZH.md)
|
||||
[简体中文](README_ZH.md)
|
||||
|
||||
## Purpose
|
||||
|
||||
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. With Go, this can be done with an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows.
|
||||
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. With Go, this can be done with an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, Windows and ARM.
|
||||
|
||||
## Overview
|
||||
|
||||
- Please see the [Documentation](http://gogs.io/docs/intro) for project design, known issues, and change log.
|
||||
- Please see the [Documentation](http://gogs.io/docs/intro) for common usages and change log.
|
||||
- See the [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
|
||||
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs) or go down to the **Installation -> Install from binary** section!
|
||||
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.html).
|
||||
@@ -63,13 +64,13 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- CI integration: [Drone](https://github.com/drone/drone)
|
||||
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb)
|
||||
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Multi-language support ([14 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
## System Requirements
|
||||
|
||||
- A cheap Raspberry Pi is powerful enough for basic functionality.
|
||||
- At least 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
- 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
|
||||
## Browser Support
|
||||
|
||||
@@ -92,6 +93,7 @@ There are 5 ways to install Gogs:
|
||||
|
||||
- [How To Set Up Gogs on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-gogs-on-ubuntu-14-04)
|
||||
- [Run your own GitHub-like service with the help of Docker](http://blog.hypriot.com/post/run-your-own-github-like-service-with-docker/)
|
||||
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs) (Chinese)
|
||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
|
||||
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
|
||||
- [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/)
|
||||
@@ -120,6 +122,7 @@ There are 5 ways to install Gogs:
|
||||
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
|
||||
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
|
||||
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
|
||||
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
+6
-4
@@ -1,15 +1,15 @@
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs)
|
||||
=====================
|
||||
|
||||
Gogs (Go Git Service) 是一款可轻易搭建的自助 Git 服务。
|
||||
Gogs (Go Git Service) 是一款极易搭建的自助 Git 服务。
|
||||
|
||||
## 开发目的
|
||||
|
||||
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。
|
||||
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X、Windows 以及 ARM 平台。
|
||||
|
||||
## 项目概览
|
||||
|
||||
- 有关项目设计、已知问题和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
|
||||
- 想要先睹为快?通过 [在线体验](https://try.gogs.io/gogs/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.html) 页面获取帮助。
|
||||
@@ -30,7 +30,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- 支持邮件服务
|
||||
- 支持后台管理面板
|
||||
- 支持 CI 集成:[Drone](https://github.com/drone/drone)
|
||||
- 支持 MySQL、PostgreSQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb) 数据库
|
||||
- 支持 MySQL、PostgreSQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
|
||||
- 支持多语言本地化([14 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
## 系统要求
|
||||
@@ -57,6 +57,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
|
||||
### 使用教程
|
||||
|
||||
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs)
|
||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654)
|
||||
|
||||
### 云端部署
|
||||
@@ -79,6 +80,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
|
||||
- 感谢 [DigitalOcean](https://www.digitalocean.com) 提供主站和体验站点的服务器赞助。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
|
||||
+6
-6
@@ -32,12 +32,12 @@ var CmdCert = cli.Command{
|
||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||
Action: runCert,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"host", "", "Comma-separated hostnames and IPs to generate a certificate for", ""},
|
||||
cli.StringFlag{"ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", ""},
|
||||
cli.IntFlag{"rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set", ""},
|
||||
cli.StringFlag{"start-date", "", "Creation date formatted as Jan 1 15:04:05 2011", ""},
|
||||
cli.DurationFlag{"duration", 365 * 24 * time.Hour, "Duration that certificate is valid for", ""},
|
||||
cli.BoolFlag{"ca", "whether this cert should be its own Certificate Authority", ""},
|
||||
stringFlag("host", "", "Comma-separated hostnames and IPs to generate a certificate for"),
|
||||
stringFlag("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521"),
|
||||
intFlag("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set"),
|
||||
stringFlag("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011"),
|
||||
durationFlag("duration", 365*24*time.Hour, "Duration that certificate is valid for"),
|
||||
boolFlag("ca", "whether this cert should be its own Certificate Authority"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func stringFlag(name, value, usage string) cli.StringFlag {
|
||||
return cli.StringFlag{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
|
||||
func boolFlag(name, usage string) cli.BoolFlag {
|
||||
return cli.BoolFlag{
|
||||
Name: name,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
|
||||
func intFlag(name string, value int, usage string) cli.IntFlag {
|
||||
return cli.IntFlag{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
|
||||
func durationFlag(name string, value time.Duration, usage string) cli.DurationFlag {
|
||||
return cli.DurationFlag{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -25,8 +25,8 @@ var CmdDump = cli.Command{
|
||||
It can be used for backup and capture Gogs server image to send to maintainer`,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
cli.BoolFlag{"verbose, v", "show process details", ""},
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
boolFlag("verbose, v", "show process details"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+58
-38
@@ -33,7 +33,7 @@ var CmdServ = cli.Command{
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -74,7 +74,51 @@ var (
|
||||
|
||||
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
return
|
||||
}
|
||||
|
||||
log.GitLogger.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func handleUpdateTask(uuid string, user *models.User, repoUserName, repoName string) {
|
||||
task, err := models.GetUpdateTaskByUUID(uuid)
|
||||
if err != nil {
|
||||
if models.IsErrUpdateTaskNotExist(err) {
|
||||
log.GitLogger.Trace("No update task is presented: %s", uuid)
|
||||
return
|
||||
}
|
||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID,
|
||||
user.Name, repoUserName, repoName, user.Id); err != nil {
|
||||
log.GitLogger.Error(2, "Update: %v", err)
|
||||
}
|
||||
|
||||
if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
|
||||
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
// Ask for running deliver hook and test pull request tasks.
|
||||
reqURL := setting.AppUrl + repoUserName + "/" + repoName + "/tasks/trigger?branch=" +
|
||||
strings.TrimPrefix(task.RefName, "refs/heads/")
|
||||
log.GitLogger.Trace("Trigger task: %s", reqURL)
|
||||
|
||||
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).Response()
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code")
|
||||
}
|
||||
} else {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runServ(c *cli.Context) {
|
||||
@@ -95,13 +139,13 @@ func runServ(c *cli.Context) {
|
||||
}
|
||||
|
||||
verb, args := parseCmd(cmd)
|
||||
repoPath := strings.Trim(args, "'")
|
||||
repoPath := strings.ToLower(strings.Trim(args, "'"))
|
||||
rr := strings.SplitN(repoPath, "/", 2)
|
||||
if len(rr) != 2 {
|
||||
fail("Invalid repository path", "Invalid repository path: %v", args)
|
||||
}
|
||||
repoUserName := rr[0]
|
||||
repoName := strings.TrimSuffix(rr[1], ".git")
|
||||
repoUserName := strings.ToLower(rr[0])
|
||||
repoName := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
|
||||
|
||||
repoUser, err := models.GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
@@ -124,6 +168,11 @@ func runServ(c *cli.Context) {
|
||||
fail("Unknown git command", "Unknown git command %s", verb)
|
||||
}
|
||||
|
||||
// Prohibit push to mirror repositories.
|
||||
if requestedMode > models.ACCESS_MODE_READ && repo.IsMirror {
|
||||
fail("mirror repository is read-only", "")
|
||||
}
|
||||
|
||||
// Allow anonymous clone for public repositories.
|
||||
var (
|
||||
keyID int64
|
||||
@@ -132,12 +181,12 @@ func runServ(c *cli.Context) {
|
||||
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
|
||||
keys := strings.Split(c.Args()[0], "-")
|
||||
if len(keys) != 2 {
|
||||
fail("Key ID format error", "Invalid key ID: %s", c.Args()[0])
|
||||
fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
|
||||
}
|
||||
|
||||
key, err := models.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64())
|
||||
if err != nil {
|
||||
fail("Key ID format error", "Invalid key ID[%s]: %v", c.Args()[0], err)
|
||||
fail("Invalid key ID", "Invalid key ID[%s]: %v", c.Args()[0], err)
|
||||
}
|
||||
keyID = key.ID
|
||||
|
||||
@@ -162,7 +211,7 @@ func runServ(c *cli.Context) {
|
||||
fail("Internal error", "UpdateDeployKey: %v", err)
|
||||
}
|
||||
} else {
|
||||
user, err = models.GetUserByKeyId(key.ID)
|
||||
user, err = models.GetUserByKeyID(key.ID)
|
||||
if err != nil {
|
||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
||||
}
|
||||
@@ -201,36 +250,7 @@ func runServ(c *cli.Context) {
|
||||
}
|
||||
|
||||
if requestedMode == models.ACCESS_MODE_WRITE {
|
||||
task, err := models.GetUpdateTaskByUUID(uuid)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID,
|
||||
user.Name, repoUserName, repoName, user.Id); err != nil {
|
||||
log.GitLogger.Error(2, "Update: %v", err)
|
||||
}
|
||||
|
||||
if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
|
||||
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
// Ask for running deliver hook and test pull request tasks.
|
||||
reqURL := setting.AppUrl + repoUserName + "/" + repoName + "/tasks/trigger?branch=" +
|
||||
strings.TrimPrefix(task.RefName, "refs/heads/")
|
||||
log.GitLogger.Trace("Trigger task: %s", reqURL)
|
||||
|
||||
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).Response()
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code")
|
||||
}
|
||||
} else {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: %v", err)
|
||||
}
|
||||
handleUpdateTask(uuid, user, repoUserName, repoName)
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
|
||||
+2
-4
@@ -20,7 +20,7 @@ var CmdUpdate = cli.Command{
|
||||
Description: `Update get pushed info and insert into database`,
|
||||
Action: runUpdate,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -42,10 +42,8 @@ func runUpdate(c *cli.Context) {
|
||||
log.GitLogger.Fatal(2, "refName is empty, shouldn't use")
|
||||
}
|
||||
|
||||
uuid := os.Getenv("uuid")
|
||||
|
||||
task := models.UpdateTask{
|
||||
UUID: uuid,
|
||||
UUID: os.Getenv("uuid"),
|
||||
RefName: args[0],
|
||||
OldCommitID: args[1],
|
||||
NewCommitID: args[2],
|
||||
|
||||
+35
-18
@@ -7,7 +7,7 @@ package cmd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
gotmpl "html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
@@ -35,11 +35,11 @@ import (
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/auth/apiv1"
|
||||
"github.com/gogits/gogs/modules/avatar"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bindata"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/template"
|
||||
"github.com/gogits/gogs/routers"
|
||||
"github.com/gogits/gogs/routers/admin"
|
||||
"github.com/gogits/gogs/routers/api/v1"
|
||||
@@ -56,8 +56,8 @@ var CmdWeb = cli.Command{
|
||||
and it takes care of all the other things for you`,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"port, p", "3000", "Temporary port number to prevent conflict", ""},
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
stringFlag("port, p", "3000", "Temporary port number to prevent conflict"),
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -80,13 +80,14 @@ func checkVersion() {
|
||||
|
||||
// Check dependency version.
|
||||
checkers := []VerChecker{
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.3.0806"},
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.4.1029"},
|
||||
{"github.com/Unknwon/macaron", macaron.Version, "0.5.4"},
|
||||
{"github.com/macaron-contrib/binding", binding.Version, "0.1.0"},
|
||||
{"github.com/macaron-contrib/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.3"},
|
||||
{"github.com/macaron-contrib/i18n", i18n.Version, "0.0.7"},
|
||||
{"github.com/macaron-contrib/session", session.Version, "0.1.6"},
|
||||
{"github.com/go-macaron/binding", binding.Version, "0.1.0"},
|
||||
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/go-macaron/csrf", csrf.Version, "0.0.3"},
|
||||
{"github.com/go-macaron/i18n", i18n.Version, "0.0.7"},
|
||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.3.4"},
|
||||
}
|
||||
for _, c := range checkers {
|
||||
@@ -124,7 +125,7 @@ func newMacaron() *macaron.Macaron {
|
||||
))
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
Funcs: []template.FuncMap{base.TemplateFuncs},
|
||||
Funcs: []gotmpl.FuncMap{template.Funcs},
|
||||
IndentJSON: macaron.Env != macaron.PROD,
|
||||
}))
|
||||
|
||||
@@ -237,6 +238,13 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), v1.EditRepoHook)
|
||||
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
|
||||
m.Get("/archive/*", v1.GetRepoArchive)
|
||||
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").Get(v1.ListRepoDeployKeys).
|
||||
Post(bind(api.CreateDeployKeyOption{}), v1.CreateRepoDeployKey)
|
||||
m.Combo("/:id").Get(v1.GetRepoDeployKey).
|
||||
Delete(v1.DeleteRepoDeploykey)
|
||||
})
|
||||
}, middleware.ApiRepoAssignment())
|
||||
}, middleware.ApiReqToken())
|
||||
|
||||
@@ -385,8 +393,8 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("/teams", org.Teams)
|
||||
m.Get("/teams/:team", org.TeamMembers)
|
||||
m.Get("/teams/:team/repositories", org.TeamRepositories)
|
||||
m.Get("/teams/:team/action/:action", org.TeamsAction)
|
||||
m.Get("/teams/:team/action/repo/:action", org.TeamsRepoAction)
|
||||
m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
|
||||
m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
|
||||
}, middleware.OrgAssignment(true, true))
|
||||
|
||||
m.Group("/:org", func() {
|
||||
@@ -462,8 +470,10 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Post("/delete", repo.DeleteDeployKey)
|
||||
})
|
||||
|
||||
}, func(ctx *middleware.Context) {
|
||||
ctx.Data["PageIsSettings"] = true
|
||||
})
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin)
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin, middleware.RepoRef())
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/action/:action", repo.Action)
|
||||
@@ -504,6 +514,7 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||
m.Get("/edit/:tagname", repo.EditRelease)
|
||||
m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
m.Post("/delete", repo.DeleteRelease)
|
||||
}, reqRepoAdmin, middleware.RepoRef())
|
||||
|
||||
m.Combo("/compare/*").Get(repo.CompareAndPullRequest).
|
||||
@@ -511,11 +522,17 @@ func runWeb(ctx *cli.Context) {
|
||||
}, reqSignIn, middleware.RepoAssignment(true))
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/releases", middleware.RepoRef(), repo.Releases)
|
||||
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
|
||||
m.Group("", func() {
|
||||
m.Get("/releases", repo.Releases)
|
||||
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
}, middleware.RepoRef(),
|
||||
func(ctx *middleware.Context) {
|
||||
ctx.Data["PageIsList"] = true
|
||||
})
|
||||
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
|
||||
m.Get("/branches", repo.Branches)
|
||||
m.Get("/archive/*", repo.Download)
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ HTTP_ADDR =
|
||||
HTTP_PORT = 3000
|
||||
; Disable SSH feature when not available
|
||||
DISABLE_SSH = false
|
||||
; Whether use builtin SSH server or not.
|
||||
START_SSH_SERVER = false
|
||||
SSH_PORT = 22
|
||||
; Disable CDN even in "prod" mode
|
||||
OFFLINE_MODE = false
|
||||
@@ -116,6 +118,16 @@ DISABLE_MINIMUM_KEY_SIZE_CHECK = false
|
||||
; Enable captcha validation for registration
|
||||
ENABLE_CAPTCHA = true
|
||||
|
||||
; used to filter keys which are too short
|
||||
[service.minimum_key_sizes]
|
||||
ED25519 = 256
|
||||
ECDSA = 256
|
||||
NTRU = 1087
|
||||
MCE = 1702
|
||||
McE = 1702
|
||||
RSA = 1024
|
||||
DSA = 1024
|
||||
|
||||
[webhook]
|
||||
; Hook task queue length
|
||||
QUEUE_LENGTH = 1000
|
||||
@@ -322,3 +334,5 @@ it-IT = it
|
||||
|
||||
[other]
|
||||
SHOW_FOOTER_BRANDING = false
|
||||
; Show version information about gogs and go in the footer
|
||||
SHOW_FOOTER_VERSION = true
|
||||
+27
-18
@@ -1,20 +1,29 @@
|
||||
# This file lists all PUBLIC individuals having contributed content to the translation.
|
||||
# Order of name is meaningless.
|
||||
# Entries are in alphabetical order.
|
||||
|
||||
Akihiro YAGASAKI <yaggytter@momiage.com>
|
||||
Alexander Steinhöfer <kontakt@lx-s.de>
|
||||
Alexandre Magno <alexandre.mbm@gmail.com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer@gmail.com>
|
||||
Christoph Kisfeld <christoph.kisfeld@gmail.com>
|
||||
Daniel Speichert <daniel@speichert.pl>
|
||||
Gregor Santner <gdev@live.de>
|
||||
Huimin Wang <wanghm2009@hotmail.co.jp>
|
||||
ilko <email>
|
||||
Thomas Fanninger <gogs.thomas@fanninger.at>
|
||||
Łukasz Jan Niemier <lukasz@niemier.pl>
|
||||
Lafriks <lafriks@gmail.com>
|
||||
Luc Stepniewski <luc@stepniewski.fr>
|
||||
Miguel de la Cruz <miguel@mcrx.me>
|
||||
Marc Schiller <marc@schiller.im>
|
||||
Morten Sørensen <klim8d@gmail.com>
|
||||
Natan Albuquerque <natanalbuquerque5@gmail.com>
|
||||
Akihiro YAGASAKI <yaggytter AT momiage DOT com>
|
||||
Alexander Steinhöfer <kontakt AT lx-s DOT de>
|
||||
Alexandre Magno <alexandre DOT mbm AT gmail DOT com>
|
||||
Andrey Nering <andrey AT nering DOT com DOT br>
|
||||
Arthur Aslanyan <arthur DOT e DOT aslanyan AT gmail DOT com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer AT gmail DOT com>
|
||||
Christoph Kisfeld <christoph DOT kisfeld AT gmail DOT com>
|
||||
Daniel Speichert <daniel AT speichert DOT pl>
|
||||
Dmitriy Nogay <me AT catwhocode DOT ga>
|
||||
Gregor Santner <gdev AT live DOT de>
|
||||
Hamid Feizabadi <hamidfzm AT gmail DOT com>
|
||||
Huimin Wang <wanghm2009 AT hotmail DOT co DOT jp>
|
||||
ilko
|
||||
Lafriks <lafriks AT gmail DOT com>
|
||||
Lauri Ojansivu <x AT xet7 DOT org>
|
||||
Luc Stepniewski <luc AT stepniewski DOT fr>
|
||||
Marc Schiller <marc AT schiller DOT im>
|
||||
Miguel de la Cruz <miguel AT mcrx DOT me>
|
||||
Morten Sørensen <klim8d AT gmail DOT com>
|
||||
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
|
||||
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
|
||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||
Tilmann Bach <tilmann AT outlook DOT com>
|
||||
Vladimir Vissoultchev <wqweto AT gmail DOT com>
|
||||
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
|
||||
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
|
||||
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
@@ -148,7 +148,6 @@ forgot_password= Forgot Password
|
||||
forget_password = Forgot password?
|
||||
sign_up_now = Need an account? Sign up now.
|
||||
confirmation_mail_sent_prompt = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
|
||||
sign_in_to_account = Sign in to your account
|
||||
active_your_account = Activate Your Account
|
||||
resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
|
||||
has_unconfirmed_mail = Hi %s, you have an unconfirmed e-mail address (<b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
|
||||
@@ -165,6 +164,7 @@ activate_account = Please activate your account
|
||||
activate_email = Verify your e-mail address
|
||||
reset_password = Reset your password
|
||||
register_success = Register success, Welcome
|
||||
register_notify = Welcome on board
|
||||
|
||||
[modal]
|
||||
yes = Yes
|
||||
@@ -337,6 +337,7 @@ visibility = Visibility
|
||||
visiblity_helper = This repository is <span class="ui red text">Private</span>
|
||||
visiblity_helper_forced = Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper = (Change of this value will affect all forks)
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
fork_repo = Fork Repository
|
||||
fork_from = Fork From
|
||||
fork_visiblity_helper = You cannot alter the visibility of a forked repository.
|
||||
@@ -351,6 +352,9 @@ auto_init = Initialize this repository with selected files and template
|
||||
create_repo = Create Repository
|
||||
default_branch = Default Branch
|
||||
mirror_interval = Mirror Interval (hour)
|
||||
watchers = Watchers
|
||||
stargazers = Stargazers
|
||||
forks = Forks
|
||||
|
||||
form.name_reserved = Repository name '%s' is reserved.
|
||||
form.name_pattern_not_allowed = Repository name pattern '%s' is not allowed.
|
||||
@@ -361,16 +365,16 @@ migrate_type_helper = This repository will be a <span class="text blue">mirror</
|
||||
migrate_repo = Migrate Repository
|
||||
migrate.clone_address = Clone Address
|
||||
migrate.clone_address_desc = This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.permission_denied = You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path = Invalid local path, it does not exist or not a directory.
|
||||
migrate.failed = Migration failed: %v
|
||||
|
||||
forked_from = forked from
|
||||
fork_from_self = You cannot fork repository you already owned!
|
||||
copy_link = Copy
|
||||
copy_link_success = Copied!
|
||||
copy_link_error = Press ⌘-C or Ctrl-C to copy
|
||||
click_to_copy = Copy to clipboard
|
||||
copied = Copied OK
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
unwatch = Unwatch
|
||||
watch = Watch
|
||||
unstar = Unstar
|
||||
@@ -384,10 +388,9 @@ create_new_repo_command = Create a new repository on the command line
|
||||
push_exist_repo = Push an existing repository from the command line
|
||||
repo_is_empty = This repository is empty, please come back later!
|
||||
|
||||
|
||||
branch = Branch
|
||||
tree = Tree
|
||||
branch_and_tags = Branches & Tags
|
||||
filter_branch_and_tag = Filter branch or tag
|
||||
branches = Branches
|
||||
tags = Tags
|
||||
issues = Issues
|
||||
@@ -456,9 +459,9 @@ issues.num_comments = %d comments
|
||||
issues.commented_at = `commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content = There is no content yet.
|
||||
issues.close_issue = Close
|
||||
issues.close_comment_issue = Close and comment
|
||||
issues.close_comment_issue = Comment and close
|
||||
issues.reopen_issue = Reopen
|
||||
issues.reopen_comment_issue = Reopen and comment
|
||||
issues.reopen_comment_issue = Comment and reopen
|
||||
issues.create_comment = Comment
|
||||
issues.closed_at = `closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at = `reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
@@ -482,6 +485,7 @@ issues.label_deletion = Label Deletion
|
||||
issues.label_deletion_desc = Delete this label will remove its information in all related issues. Do you want to continue?
|
||||
issues.label_deletion_success = Label has been deleted successfully!
|
||||
|
||||
pulls.new = New Pull Request
|
||||
pulls.compare_changes = Compare Changes
|
||||
pulls.compare_changes_desc = Compare two branches and make a pull request for changes.
|
||||
pulls.compare_base = base
|
||||
@@ -520,7 +524,7 @@ milestones.title = Title
|
||||
milestones.desc = Description
|
||||
milestones.due_date = Due Date (optional)
|
||||
milestones.clear = Clear
|
||||
milestones.invalid_due_date_format = Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.invalid_due_date_format = Due date format is invalid, must be 'yyyy-mm-dd'.
|
||||
milestones.create_success = Milestone '%s' has been created successfully!
|
||||
milestones.edit = Edit Milestone
|
||||
milestones.edit_subheader = Use better description for milestones so people won't be confused.
|
||||
@@ -562,6 +566,7 @@ settings.confirm_delete = Confirm Deletion
|
||||
settings.add_collaborator = Add New Collaborator
|
||||
settings.add_collaborator_success = New collaborator has been added.
|
||||
settings.remove_collaborator_success = Collaborator has been removed.
|
||||
settings.search_user_placeholder = Search user...
|
||||
settings.user_is_org_member = User is organization member who cannot be added as a collaborator.
|
||||
settings.add_webhook = Add Webhook
|
||||
settings.hooks_desc = Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in Gogs, we will handle the notification to the target host you specify. Learn more in this <a target="_blank" href="%s">Webhooks Guide</a>.
|
||||
@@ -634,24 +639,32 @@ release.stable = Stable
|
||||
release.edit = edit
|
||||
release.ahead = <strong>%d</strong> commits to %s since this release
|
||||
release.source_code = Source Code
|
||||
release.new_subheader = Publish releases to iterate product.
|
||||
release.edit_subheader = Detailed change log can help users understand what has been improved.
|
||||
release.tag_name = Tag name
|
||||
release.target = Target
|
||||
release.tag_helper = Choose an existing tag, or create a new tag on publish.
|
||||
release.release_title = Release title
|
||||
release.content_with_md = Content with <a href="%s">Markdown</a>
|
||||
release.title = Title
|
||||
release.content = Content
|
||||
release.write = Write
|
||||
release.preview = Preview
|
||||
release.content_placeholder = Write some content
|
||||
release.loading = Loading...
|
||||
release.prerelease_desc = This is a pre-release
|
||||
release.prerelease_helper = We’ll point out that this release is not production-ready.
|
||||
release.cancel = Cancel
|
||||
release.publish = Publish Release
|
||||
release.save_draft = Save Draft
|
||||
release.edit_release = Edit Release
|
||||
release.delete_release = Delete This Release
|
||||
release.deletion = Release Deletion
|
||||
release.deletion_desc = Delete this release will delete corresponding Git tag. Do you want to continue?
|
||||
release.deletion_success = Release has been deleted successfully!
|
||||
release.tag_name_already_exist = Release with this tag name has already existed.
|
||||
release.downloads = Downloads
|
||||
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
org_full_name_holder = Organization Full Name
|
||||
org_name_helper = Great organization names are short and memorable.
|
||||
create_org = Create Organization
|
||||
repo_updated = Updated
|
||||
@@ -688,16 +701,17 @@ settings.delete_org_title = Organization Deletion
|
||||
settings.delete_org_desc = This organization is going to be deleted permanently, do you want to continue?
|
||||
settings.hooks_desc = Add webhooks that will be triggered for <strong>all repositories</strong> under this organization.
|
||||
|
||||
members.membership_visibility = Membership Visibility:
|
||||
members.public = Public
|
||||
members.public_helper = make private
|
||||
members.private = Private
|
||||
members.private_helper = make public
|
||||
members.member_role = Member Role:
|
||||
members.owner = Owner
|
||||
members.member = Member
|
||||
members.conceal = Conceal
|
||||
members.remove = Remove
|
||||
members.leave = Leave
|
||||
members.invite_desc = Start typing a username to invite a new member to %s:
|
||||
members.invite_desc = Add a new member to %s:
|
||||
members.invite_now = Invite Now
|
||||
|
||||
teams.join = Join
|
||||
@@ -722,6 +736,7 @@ teams.read_permission_desc = This team grants <strong>Read</strong> access: memb
|
||||
teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to the team's repositories.
|
||||
teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories.
|
||||
teams.repositories = Team Repositories
|
||||
teams.search_repo_placeholder = Search repository...
|
||||
teams.add_team_repository = Add Team Repository
|
||||
teams.remove_repo = Remove
|
||||
teams.add_nonexistent_repo = The repository you're trying to add does not exist, please create it first.
|
||||
@@ -752,6 +767,8 @@ dashboard.delete_inactivate_accounts = Delete all inactive accounts
|
||||
dashboard.delete_inactivate_accounts_success = All inactivate accounts have been deleted successfully.
|
||||
dashboard.delete_repo_archives = Delete all repositories archives
|
||||
dashboard.delete_repo_archives_success = All repositories archives have been deleted successfully.
|
||||
dashboard.delete_missing_repos = Delete all repository records that lost Git files
|
||||
dashboard.delete_missing_repos_success = All repository records that lost Git files have been deleted successfully.
|
||||
dashboard.git_gc_repos = Do garbage collection on repositories
|
||||
dashboard.git_gc_repos_success = All repositories have done garbage collection successfully.
|
||||
dashboard.resync_all_sshkeys = Rewrite '.ssh/authorized_keys' file (caution: non-Gogs keys will be lost)
|
||||
@@ -808,6 +825,7 @@ users.edit_account = Edit Account
|
||||
users.is_activated = This account is activated
|
||||
users.is_admin = This account has administrator permissions
|
||||
users.allow_git_hook = This account has permissions to create Git hooks
|
||||
users.allow_import_local = This account has permissions to import local repositories
|
||||
users.update_profile = Update Account Profile
|
||||
users.delete_account = Delete This Account
|
||||
users.still_own_repo = This account still has ownership over at least one repository, you have to delete or transfer them first.
|
||||
@@ -955,7 +973,7 @@ notices.delete_success = System notice has been deleted successfully.
|
||||
[action]
|
||||
create_repo = created repository <a href="%s">%s</a>
|
||||
rename_repo = renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
commit_repo = pushed to <a href="%s/src/%s">%[2]s</a> at <a href="%[1]s">%[3]s</a>
|
||||
commit_repo = pushed to <a href="%[1]s/src/%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a>
|
||||
create_issue = `opened issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
create_pull_request = `created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue = `commented on issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
|
||||
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1009
-992
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -464,7 +464,7 @@
|
||||
"ignore": 0,
|
||||
"ignoreWasSetByUser": 0,
|
||||
"inputAbbreviatedPath": "\/public\/less\/gogs.less",
|
||||
"outputAbbreviatedPath": "\/public\/css\/gogs.min.css",
|
||||
"outputAbbreviatedPath": "\/public\/css\/gogs.css",
|
||||
"outputPathIsOutsideProject": 0,
|
||||
"outputPathIsSetByUser": 1,
|
||||
"outputStyle": 1,
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
web:
|
||||
build: .
|
||||
links:
|
||||
- mysql
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
mysql:
|
||||
image: mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=gogs
|
||||
- MYSQL_DATABASE=gogs
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Set temp environment vars
|
||||
export GOPATH=/tmp/go
|
||||
|
||||
@@ -20,3 +20,4 @@ ln -sf /data/gogs/data ./data
|
||||
ln -sf /data/git /home/git
|
||||
|
||||
chown -R git:git /data /app/gogs ~git/
|
||||
chmod 0755 /data /data/gogs ~git/
|
||||
@@ -23,4 +23,5 @@ fi
|
||||
|
||||
# Set correct right to ssh keys
|
||||
chown -R root:root /data/ssh/*
|
||||
chmod 600 /data/ssh/*
|
||||
chmod 0700 /data/ssh
|
||||
chmod 0600 /data/ssh/*
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
exec gosu root /sbin/syslogd -nS -O-
|
||||
+47
-31
@@ -1,40 +1,56 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Cleanup SOCAT services and s6 event folder
|
||||
# On start and on shutdown in case container has been killed
|
||||
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
|
||||
rm -rf /app/gogs/docker/s6/SOCAT_*
|
||||
|
||||
# Create VOLUME subfolder
|
||||
for f in /data/gogs/data /data/gogs/conf /data/gogs/log /data/git /data/ssh; do
|
||||
if ! test -d $f; then
|
||||
mkdir -p $f
|
||||
fi
|
||||
done
|
||||
|
||||
# Bind linked docker container to localhost socket using socat
|
||||
USED_PORT="3000:22"
|
||||
while read NAME ADDR PORT; do
|
||||
if test -z "$NAME$ADDR$PORT"; then
|
||||
continue
|
||||
elif echo $USED_PORT | grep -E "(^|:)$PORT($|:)" > /dev/null; then
|
||||
echo "init:socat | Can't bind linked container ${NAME} to localhost, port ${PORT} already in use" 1>&2
|
||||
else
|
||||
SERV_FOLDER=/app/gogs/docker/s6/SOCAT_${NAME}_${PORT}
|
||||
mkdir -p ${SERV_FOLDER}
|
||||
CMD="socat -ls TCP4-LISTEN:${PORT},fork,reuseaddr TCP4:${ADDR}:${PORT}"
|
||||
echo -e "#!/bin/sh\nexec $CMD" > ${SERV_FOLDER}/run
|
||||
chmod +x ${SERV_FOLDER}/run
|
||||
USED_PORT="${USED_PORT}:${PORT}"
|
||||
echo "init:socat | Linked container ${NAME} will be binded to localhost on port ${PORT}" 1>&2
|
||||
fi
|
||||
done << EOT
|
||||
$(env | sed -En 's|(.*)_PORT_([0-9]+)_TCP=tcp://(.*):([0-9]+)|\1 \3 \4|p')
|
||||
create_socat_links() {
|
||||
# Bind linked docker container to localhost socket using socat
|
||||
USED_PORT="3000:22"
|
||||
while read NAME ADDR PORT; do
|
||||
if test -z "$NAME$ADDR$PORT"; then
|
||||
continue
|
||||
elif echo $USED_PORT | grep -E "(^|:)$PORT($|:)" > /dev/null; then
|
||||
echo "init:socat | Can't bind linked container ${NAME} to localhost, port ${PORT} already in use" 1>&2
|
||||
else
|
||||
SERV_FOLDER=/app/gogs/docker/s6/SOCAT_${NAME}_${PORT}
|
||||
mkdir -p ${SERV_FOLDER}
|
||||
CMD="socat -ls TCP4-LISTEN:${PORT},fork,reuseaddr TCP4:${ADDR}:${PORT}"
|
||||
echo -e "#!/bin/sh\nexec $CMD" > ${SERV_FOLDER}/run
|
||||
chmod +x ${SERV_FOLDER}/run
|
||||
USED_PORT="${USED_PORT}:${PORT}"
|
||||
echo "init:socat | Linked container ${NAME} will be binded to localhost on port ${PORT}" 1>&2
|
||||
fi
|
||||
done << EOT
|
||||
$(env | sed -En 's|(.*)_PORT_([0-9]+)_TCP=tcp://(.*):([0-9]+)|\1 \3 \4|p')
|
||||
EOT
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
# Cleanup SOCAT services and s6 event folder
|
||||
# On start and on shutdown in case container has been killed
|
||||
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
|
||||
rm -rf /app/gogs/docker/s6/SOCAT_*
|
||||
}
|
||||
|
||||
create_volume_subfolder() {
|
||||
# Create VOLUME subfolder
|
||||
for f in /data/gogs/data /data/gogs/conf /data/gogs/log /data/git /data/ssh; do
|
||||
if ! test -d $f; then
|
||||
mkdir -p $f
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cleanup
|
||||
create_volume_subfolder
|
||||
|
||||
LINK=$(echo "$SOCAT_LINK" | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$LINK" = "false" -o "$LINK" = "0" ]; then
|
||||
echo "init:socat | Will not try to create socat links as requested" 1>&2
|
||||
else
|
||||
create_socat_links
|
||||
fi
|
||||
|
||||
# Exec CMD or S6 by default if nothing present
|
||||
if [ $# -gt 0 ];then
|
||||
exec "$@"
|
||||
else
|
||||
exec /usr/bin/s6-svscan /app/gogs/docker/s6/
|
||||
exec /bin/s6-svscan /app/gogs/docker/s6/
|
||||
fi
|
||||
@@ -4,7 +4,7 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
|
||||
// Gogs (Go Git Service) is a painless self-hosted Git Service.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.6.18.1029 Beta"
|
||||
const APP_VER = "0.7.20.1121 Beta"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
+33
-17
@@ -36,19 +36,19 @@ func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
|
||||
mode = ACCESS_MODE_READ
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
if u.Id == repo.OwnerID {
|
||||
return ACCESS_MODE_OWNER, nil
|
||||
}
|
||||
|
||||
a := &Access{UserID: u.Id, RepoID: repo.ID}
|
||||
if has, err := e.Get(a); !has || err != nil {
|
||||
return mode, err
|
||||
}
|
||||
return a.Mode, nil
|
||||
if u == nil {
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
return mode, nil
|
||||
if u.Id == repo.OwnerID {
|
||||
return ACCESS_MODE_OWNER, nil
|
||||
}
|
||||
|
||||
a := &Access{UserID: u.Id, RepoID: repo.ID}
|
||||
if has, err := e.Get(a); !has || err != nil {
|
||||
return mode, err
|
||||
}
|
||||
return a.Mode, nil
|
||||
}
|
||||
|
||||
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
||||
@@ -67,9 +67,8 @@ func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
return hasAccess(x, u, repo, testMode)
|
||||
}
|
||||
|
||||
// GetAccessibleRepositories finds all repositories where a user has access to,
|
||||
// besides he/she owns.
|
||||
func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
|
||||
func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
|
||||
return nil, err
|
||||
@@ -80,7 +79,7 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||
repo, err := GetRepositoryByID(access.RepoID)
|
||||
if err != nil {
|
||||
if IsErrRepoNotExist(err) {
|
||||
log.Error(4, "%v", err)
|
||||
log.Error(4, "GetRepositoryByID: %v", err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
@@ -92,11 +91,28 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||
}
|
||||
repos[repo] = access.Mode
|
||||
}
|
||||
|
||||
// FIXME: should we generate an ordered list here? Random looks weird.
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetAccessibleRepositories finds all repositories where a user has access but does not own.
|
||||
func (u *User) GetAccessibleRepositories() ([]*Repository, error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(accesses) == 0 {
|
||||
return []*Repository{}, nil
|
||||
}
|
||||
|
||||
repoIDs := make([]int64, 0, len(accesses))
|
||||
for _, access := range accesses {
|
||||
repoIDs = append(repoIDs, access.RepoID)
|
||||
}
|
||||
repos := make([]*Repository, 0, len(repoIDs))
|
||||
return repos, x.Where("owner_id != ?", u.Id).In("id", repoIDs).Desc("updated").Find(&repos)
|
||||
}
|
||||
|
||||
func maxAccessMode(modes ...AccessMode) AccessMode {
|
||||
max := ACCESS_MODE_NONE
|
||||
for _, mode := range modes {
|
||||
|
||||
+75
-10
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
@@ -136,6 +137,26 @@ func (a Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 2)
|
||||
}
|
||||
|
||||
func (a Action) GetIssueTitle() string {
|
||||
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error(4, "GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
}
|
||||
return issue.Name
|
||||
}
|
||||
|
||||
func (a Action) GetIssueContent() string {
|
||||
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error(4, "GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
}
|
||||
return issue.Content
|
||||
}
|
||||
|
||||
func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: u.Id,
|
||||
@@ -147,7 +168,7 @@ func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers '%d/%s': %v", u.Id, repo.ID, err)
|
||||
return fmt.Errorf("notify watchers '%d/%d': %v", u.Id, repo.ID, err)
|
||||
}
|
||||
|
||||
log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
|
||||
@@ -187,8 +208,48 @@ func issueIndexTrimRight(c rune) bool {
|
||||
return !unicode.IsDigit(c)
|
||||
}
|
||||
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareUrl string
|
||||
|
||||
avatars map[string]string
|
||||
}
|
||||
|
||||
func NewPushCommits() *PushCommits {
|
||||
return &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// AvatarLink tries to match user in database with e-mail
|
||||
// in order to show custom avatar, and falls back to general avatar link.
|
||||
func (push *PushCommits) AvatarLink(email string) string {
|
||||
_, ok := push.avatars[email]
|
||||
if !ok {
|
||||
u, err := GetUserByEmail(email)
|
||||
if err != nil {
|
||||
push.avatars[email] = base.AvatarLink(email)
|
||||
if !IsErrUserNotExist(err) {
|
||||
log.Error(4, "GetUserByEmail: %v", err)
|
||||
}
|
||||
} else {
|
||||
push.avatars[email] = u.AvatarLink()
|
||||
}
|
||||
}
|
||||
|
||||
return push.avatars[email]
|
||||
}
|
||||
|
||||
// updateIssuesCommit checks if issues are manipulated by commit message.
|
||||
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
|
||||
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*PushCommit) error {
|
||||
// Commits are appended in the reverse order.
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
c := commits[i]
|
||||
@@ -322,7 +383,7 @@ func CommitRepoAction(
|
||||
repoID int64,
|
||||
repoUserName, repoName string,
|
||||
refFullName string,
|
||||
commit *base.PushCommits,
|
||||
commit *PushCommits,
|
||||
oldCommitID string, newCommitID string) error {
|
||||
|
||||
u, err := GetUserByID(userID)
|
||||
@@ -337,12 +398,18 @@ func CommitRepoAction(
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
|
||||
isNewBranch := false
|
||||
opType := COMMIT_REPO
|
||||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(refFullName, "refs/tags/") {
|
||||
opType = PUSH_TAG
|
||||
commit = &base.PushCommits{}
|
||||
commit = &PushCommits{}
|
||||
} else {
|
||||
// if not the first commit, set the compareUrl
|
||||
if !strings.HasPrefix(oldCommitID, "0000000") {
|
||||
@@ -351,12 +418,10 @@ func CommitRepoAction(
|
||||
isNewBranch = true
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
// NOTE: limit to detect latest 100 commits.
|
||||
if len(commit.Commits) > 100 {
|
||||
commit.Commits = commit.Commits[len(commit.Commits)-100:]
|
||||
}
|
||||
|
||||
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
|
||||
log.Error(4, "updateIssuesCommit: %v", err)
|
||||
}
|
||||
@@ -488,7 +553,7 @@ func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repos
|
||||
IsPrivate: repo.IsPrivate,
|
||||
Content: path.Join(oldOwner.LowerName, repo.LowerName),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers '%d/%s': %v", actUser.Id, repo.ID, err)
|
||||
return fmt.Errorf("notify watchers '%d/%d': %v", actUser.Id, repo.ID, err)
|
||||
}
|
||||
|
||||
// Remove watch for organization.
|
||||
|
||||
+98
-14
@@ -18,7 +18,7 @@ func IsErrNameReserved(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNameReserved) Error() string {
|
||||
return fmt.Sprintf("name is reserved: [name: %s]", err.Name)
|
||||
return fmt.Sprintf("name is reserved [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrNamePatternNotAllowed struct {
|
||||
@@ -31,7 +31,7 @@ func IsErrNamePatternNotAllowed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNamePatternNotAllowed) Error() string {
|
||||
return fmt.Sprintf("name pattern is not allowed: [pattern: %s]", err.Pattern)
|
||||
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
|
||||
}
|
||||
|
||||
// ____ ___
|
||||
@@ -51,7 +51,7 @@ func IsErrUserAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("user already exists: [name: %s]", err.Name)
|
||||
return fmt.Sprintf("user already exists [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrUserNotExist struct {
|
||||
@@ -65,7 +65,7 @@ func IsErrUserNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserNotExist) Error() string {
|
||||
return fmt.Sprintf("user does not exist: [uid: %d, name: %s]", err.UID, err.Name)
|
||||
return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
|
||||
}
|
||||
|
||||
type ErrEmailAlreadyUsed struct {
|
||||
@@ -78,7 +78,7 @@ func IsErrEmailAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail has been used: [email: %s]", err.Email)
|
||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
type ErrUserOwnRepos struct {
|
||||
@@ -91,7 +91,7 @@ func IsErrUserOwnRepos(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserOwnRepos) Error() string {
|
||||
return fmt.Sprintf("user still has ownership of repositories: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
type ErrUserHasOrgs struct {
|
||||
@@ -104,7 +104,7 @@ func IsErrUserHasOrgs(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserHasOrgs) Error() string {
|
||||
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// __________ ___. .__ .__ ____ __.
|
||||
@@ -114,6 +114,19 @@ func (err ErrUserHasOrgs) Error() string {
|
||||
// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____|
|
||||
// \/ \/ \/ \/\/
|
||||
|
||||
type ErrKeyUnableVerify struct {
|
||||
Result string
|
||||
}
|
||||
|
||||
func IsErrKeyUnableVerify(err error) bool {
|
||||
_, ok := err.(ErrKeyUnableVerify)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyUnableVerify) Error() string {
|
||||
return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result)
|
||||
}
|
||||
|
||||
type ErrKeyNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
@@ -124,7 +137,7 @@ func IsErrKeyNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
|
||||
return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
type ErrKeyAlreadyExist struct {
|
||||
@@ -138,7 +151,7 @@ func IsErrKeyAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
}
|
||||
|
||||
type ErrKeyNameAlreadyUsed struct {
|
||||
@@ -152,7 +165,22 @@ func IsErrKeyNameAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNotExist struct {
|
||||
ID int64
|
||||
KeyID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func IsErrDeployKeyNotExist(err error) bool {
|
||||
_, ok := err.(ErrDeployKeyNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyAlreadyExist struct {
|
||||
@@ -166,7 +194,7 @@ func IsErrDeployKeyAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNameAlreadyUsed struct {
|
||||
@@ -180,7 +208,7 @@ func IsErrDeployKeyNameAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
}
|
||||
|
||||
// _____ ___________ __
|
||||
@@ -200,7 +228,7 @@ func IsErrAccessTokenNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenNotExist) Error() string {
|
||||
return fmt.Sprintf("access token does not exist: [sha: %s]", err.SHA)
|
||||
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
|
||||
}
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -220,7 +248,7 @@ func IsErrLastOrgOwner(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrLastOrgOwner) Error() string {
|
||||
return fmt.Sprintf("user is the last member of owner team: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// __________ .__ __
|
||||
@@ -259,6 +287,62 @@ func (err ErrRepoAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
|
||||
}
|
||||
|
||||
type ErrInvalidCloneAddr struct {
|
||||
IsURLError bool
|
||||
IsInvalidPath bool
|
||||
IsPermissionDenied bool
|
||||
}
|
||||
|
||||
func IsErrInvalidCloneAddr(err error) bool {
|
||||
_, ok := err.(ErrInvalidCloneAddr)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidCloneAddr) Error() string {
|
||||
return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]",
|
||||
err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied)
|
||||
}
|
||||
|
||||
type ErrUpdateTaskNotExist struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
func IsErrUpdateTaskNotExist(err error) bool {
|
||||
_, ok := err.(ErrUpdateTaskNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUpdateTaskNotExist) Error() string {
|
||||
return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
|
||||
}
|
||||
|
||||
type ErrReleaseAlreadyExist struct {
|
||||
TagName string
|
||||
}
|
||||
|
||||
func IsErrReleaseAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrReleaseAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReleaseAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("Release tag already exist [tag_name: %s]", err.TagName)
|
||||
}
|
||||
|
||||
type ErrReleaseNotExist struct {
|
||||
ID int64
|
||||
TagName string
|
||||
}
|
||||
|
||||
func IsErrReleaseNotExist(err error) bool {
|
||||
_, ok := err.(ErrReleaseNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReleaseNotExist) Error() string {
|
||||
return fmt.Sprintf("Release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ __
|
||||
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
|
||||
|
||||
+28
-14
@@ -37,6 +37,7 @@ const (
|
||||
DIFF_FILE_ADD = iota + 1
|
||||
DIFF_FILE_CHANGE
|
||||
DIFF_FILE_DEL
|
||||
DIFF_FILE_RENAME
|
||||
)
|
||||
|
||||
type DiffLine struct {
|
||||
@@ -57,12 +58,14 @@ type DiffSection struct {
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
OldName string
|
||||
Index int
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
IsBin bool
|
||||
IsRenamed bool
|
||||
Sections []*DiffSection
|
||||
}
|
||||
|
||||
@@ -94,7 +97,7 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
var i int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// fmt.Println(i, line)
|
||||
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
|
||||
continue
|
||||
}
|
||||
@@ -158,17 +161,27 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
|
||||
// Get new file.
|
||||
if strings.HasPrefix(line, DIFF_HEAD) {
|
||||
beg := len(DIFF_HEAD)
|
||||
a := line[beg : (len(line)-beg)/2+beg]
|
||||
middle := -1
|
||||
|
||||
// In case file name is surrounded by double quotes(it happens only in git-shell).
|
||||
if a[0] == '"' {
|
||||
a = a[1 : len(a)-1]
|
||||
a = strings.Replace(a, `\"`, `"`, -1)
|
||||
// Note: In case file name is surrounded by double quotes (it happens only in git-shell).
|
||||
// e.g. diff --git "a/xxx" "b/xxx"
|
||||
hasQuote := line[len(DIFF_HEAD)] == '"'
|
||||
if hasQuote {
|
||||
middle = strings.Index(line, ` "b/`)
|
||||
} else {
|
||||
middle = strings.Index(line, " b/")
|
||||
}
|
||||
|
||||
beg := len(DIFF_HEAD)
|
||||
a := line[beg+2 : middle]
|
||||
b := line[middle+3:]
|
||||
if hasQuote {
|
||||
a = string(git.UnescapeChars([]byte(a[1 : len(a)-1])))
|
||||
b = string(git.UnescapeChars([]byte(b[1 : len(b)-1])))
|
||||
}
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: a[strings.Index(a, "/")+1:],
|
||||
Name: a,
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
@@ -180,16 +193,17 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
switch {
|
||||
case strings.HasPrefix(scanner.Text(), "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
curFile.IsDeleted = false
|
||||
curFile.IsCreated = true
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = false
|
||||
case strings.HasPrefix(scanner.Text(), "similarity index 100%"):
|
||||
curFile.Type = DIFF_FILE_RENAME
|
||||
curFile.IsRenamed = true
|
||||
curFile.OldName = curFile.Name
|
||||
curFile.Name = b
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
break
|
||||
@@ -244,10 +258,10 @@ func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxline
|
||||
cmd = exec.Command("git", "show", afterCommitId)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitId)
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-M", beforeCommitId, afterCommitId)
|
||||
}
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
|
||||
+9
-13
@@ -718,32 +718,28 @@ func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
|
||||
if opts.AssigneeID > 0 {
|
||||
baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
|
||||
}
|
||||
if opts.IsPull {
|
||||
baseCond += " AND issue.is_pull=1"
|
||||
} else {
|
||||
baseCond += " AND issue.is_pull=0"
|
||||
}
|
||||
baseCond += " AND issue.is_pull=?"
|
||||
|
||||
switch opts.FilterMode {
|
||||
case FM_ALL, FM_ASSIGN:
|
||||
results, _ := x.Query(queryStr+baseCond, false)
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull)
|
||||
stats.OpenCount = parseCountResult(results)
|
||||
results, _ = x.Query(queryStr+baseCond, true)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull)
|
||||
stats.ClosedCount = parseCountResult(results)
|
||||
|
||||
case FM_CREATE:
|
||||
baseCond += " AND poster_id=?"
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.UserID)
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID)
|
||||
stats.OpenCount = parseCountResult(results)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.UserID)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID)
|
||||
stats.ClosedCount = parseCountResult(results)
|
||||
|
||||
case FM_MENTION:
|
||||
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
|
||||
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.UserID, true)
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID, true)
|
||||
stats.OpenCount = parseCountResult(results)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.UserID, true)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID, true)
|
||||
stats.ClosedCount = parseCountResult(results)
|
||||
}
|
||||
return stats
|
||||
@@ -1375,8 +1371,8 @@ func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) {
|
||||
}
|
||||
|
||||
// DeleteMilestoneByID deletes a milestone by given ID.
|
||||
func DeleteMilestoneByID(mid int64) error {
|
||||
m, err := GetMilestoneByID(mid)
|
||||
func DeleteMilestoneByID(id int64) error {
|
||||
m, err := GetMilestoneByID(id)
|
||||
if err != nil {
|
||||
if IsErrMilestoneNotExist(err) {
|
||||
return nil
|
||||
|
||||
+3
-4
@@ -225,10 +225,9 @@ func DeleteSource(source *LoginSource) error {
|
||||
// |_______ \/_______ /\____|__ /____|
|
||||
// \/ \/ \/
|
||||
|
||||
// Query if name/passwd can login against the LDAP directory pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
// FIXME: https://github.com/gogits/gogs/issues/672
|
||||
// LoginUserLDAPSource queries if name/passwd can login against the LDAP directory pool,
|
||||
// and create a local user if success when enabled.
|
||||
// It returns the same LoginUserPlain semantic.
|
||||
func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
cfg := source.Cfg.(*LDAPConfig)
|
||||
directBind := (source.Type == DLDAP)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -66,6 +67,7 @@ var migrations = []Migration{
|
||||
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
|
||||
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
|
||||
NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
|
||||
NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
@@ -454,7 +456,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
|
||||
|
||||
pushCommits = new(PushCommits)
|
||||
if err = json.Unmarshal(action["content"], pushCommits); err != nil {
|
||||
return fmt.Errorf("unmarshal action content[%s]: %v", actID, err)
|
||||
return fmt.Errorf("unmarshal action content[%d]: %v", actID, err)
|
||||
}
|
||||
|
||||
infos := strings.Split(pushCommits.CompareUrl, "/")
|
||||
@@ -465,7 +467,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
|
||||
|
||||
p, err := json.Marshal(pushCommits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal action content[%s]: %v", actID, err)
|
||||
return fmt.Errorf("marshal action content[%d]: %v", actID, err)
|
||||
}
|
||||
|
||||
if _, err = sess.Id(actID).Update(&Action{
|
||||
@@ -653,3 +655,50 @@ func renamePullRequestFields(x *xorm.Engine) (err error) {
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
|
||||
type (
|
||||
User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
LowerName string
|
||||
}
|
||||
Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64
|
||||
LowerName string
|
||||
}
|
||||
)
|
||||
|
||||
repos := make([]*Repository, 0, 25)
|
||||
if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
|
||||
return fmt.Errorf("select all non-mirror repositories: %v", err)
|
||||
}
|
||||
var user *User
|
||||
for _, repo := range repos {
|
||||
user = &User{ID: repo.OwnerID}
|
||||
has, err := x.Get(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
|
||||
} else if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
|
||||
|
||||
// In case repository file is somehow missing.
|
||||
if !com.IsFile(configPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open config file: %v", err)
|
||||
}
|
||||
cfg.DeleteSection("remote \"origin\"")
|
||||
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
|
||||
return fmt.Errorf("save config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+1
-1
@@ -90,7 +90,7 @@ func init() {
|
||||
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
|
||||
new(Notice), new(EmailAddress))
|
||||
|
||||
gonicNames := []string{"UID", "SSL"}
|
||||
gonicNames := []string{"SSL"}
|
||||
for _, name := range gonicNames {
|
||||
core.LintGonicMapper[name] = true
|
||||
}
|
||||
|
||||
+75
-66
@@ -13,7 +13,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -33,25 +32,8 @@ const (
|
||||
_TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyUnableVerify = errors.New("Unable to verify public key")
|
||||
)
|
||||
|
||||
var sshOpLocker = sync.Mutex{}
|
||||
|
||||
var (
|
||||
SSHPath string // SSH directory.
|
||||
appPath string // Execution(binary) path.
|
||||
)
|
||||
|
||||
// exePath returns the executable path.
|
||||
func exePath() (string, error) {
|
||||
file, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Abs(file)
|
||||
}
|
||||
var SSHPath string // SSH directory.
|
||||
|
||||
// homeDir returns the home directory of current user.
|
||||
func homeDir() string {
|
||||
@@ -63,16 +45,9 @@ func homeDir() string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
if appPath, err = exePath(); err != nil {
|
||||
log.Fatal(4, "fail to get app path: %v\n", err)
|
||||
}
|
||||
appPath = strings.Replace(appPath, "\\", "/", -1)
|
||||
|
||||
// Determine and create .ssh path.
|
||||
SSHPath = filepath.Join(homeDir(), ".ssh")
|
||||
if err = os.MkdirAll(SSHPath, 0700); err != nil {
|
||||
if err := os.MkdirAll(SSHPath, 0700); err != nil {
|
||||
log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
|
||||
}
|
||||
}
|
||||
@@ -114,17 +89,7 @@ func (k *PublicKey) OmitEmail() string {
|
||||
|
||||
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
|
||||
func (key *PublicKey) GetAuthorizedString() string {
|
||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
|
||||
}
|
||||
|
||||
var minimumKeySizes = map[string]int{
|
||||
"(ED25519)": 256,
|
||||
"(ECDSA)": 256,
|
||||
"(NTRU)": 1087,
|
||||
"(MCE)": 1702,
|
||||
"(McE)": 1702,
|
||||
"(RSA)": 1024,
|
||||
"(DSA)": 1024,
|
||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
|
||||
}
|
||||
|
||||
func extractTypeFromBase64Key(key string) (string, error) {
|
||||
@@ -228,9 +193,9 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
tmpFile.Close()
|
||||
|
||||
// Check if ssh-keygen recognizes its contents.
|
||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
|
||||
if err != nil {
|
||||
return "", errors.New("ssh-keygen -l -f: " + stderr)
|
||||
return "", errors.New("ssh-keygen -lf: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
|
||||
}
|
||||
@@ -242,7 +207,7 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
|
||||
sshKeygenOutput := strings.Split(stdout, " ")
|
||||
if len(sshKeygenOutput) < 4 {
|
||||
return content, ErrKeyUnableVerify
|
||||
return content, ErrKeyUnableVerify{stdout}
|
||||
}
|
||||
|
||||
// Check if key type and key size match.
|
||||
@@ -251,9 +216,10 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
if keySize == 0 {
|
||||
return "", errors.New("cannot get key size of the given key")
|
||||
}
|
||||
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
|
||||
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||
return "", errors.New("sorry, unrecognized public key type")
|
||||
|
||||
keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
|
||||
if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||
return "", fmt.Errorf("unrecognized public key type: %s", keyType)
|
||||
} else if keySize < minimumKeySize {
|
||||
return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
||||
}
|
||||
@@ -321,9 +287,9 @@ func addKey(e Engine, key *PublicKey) (err error) {
|
||||
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
|
||||
if err != nil {
|
||||
return errors.New("ssh-keygen -l -f: " + stderr)
|
||||
return errors.New("ssh-keygen -lf: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return errors.New("not enough output for calculating fingerprint: " + stdout)
|
||||
}
|
||||
@@ -382,6 +348,19 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
|
||||
// and returns public key found.
|
||||
func SearchPublicKeyByContent(content string) (*PublicKey, error) {
|
||||
key := new(PublicKey)
|
||||
has, err := x.Where("content like ?", content+"%").Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrKeyNotExist{}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ListPublicKeys returns a list of public keys belongs to given user.
|
||||
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
|
||||
keys := make([]*PublicKey, 0, 5)
|
||||
@@ -540,6 +519,7 @@ type DeployKey struct {
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX"`
|
||||
Name string
|
||||
Fingerprint string
|
||||
Content string `xorm:"-"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time // Note: Updated must below Created for AfterSet.
|
||||
HasRecentActivity bool `xorm:"-"`
|
||||
@@ -554,6 +534,16 @@ func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetContent gets associated public key content.
|
||||
func (k *DeployKey) GetContent() error {
|
||||
pkey, err := GetPublicKeyByID(k.KeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.Content = pkey.Content
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
|
||||
// Note: We want error detail, not just true or false here.
|
||||
has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
|
||||
@@ -574,18 +564,19 @@ func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
|
||||
}
|
||||
|
||||
// addDeployKey adds new key-repo relation.
|
||||
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (err error) {
|
||||
if err = checkDeployKey(e, keyID, repoID, name); err != nil {
|
||||
return err
|
||||
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) {
|
||||
if err := checkDeployKey(e, keyID, repoID, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = e.Insert(&DeployKey{
|
||||
key := &DeployKey{
|
||||
KeyID: keyID,
|
||||
RepoID: repoID,
|
||||
Name: name,
|
||||
Fingerprint: fingerprint,
|
||||
})
|
||||
return err
|
||||
}
|
||||
_, err := e.Insert(key)
|
||||
return key, err
|
||||
}
|
||||
|
||||
// HasDeployKey returns true if public key is a deploy key of given repository.
|
||||
@@ -595,39 +586,52 @@ func HasDeployKey(keyID, repoID int64) bool {
|
||||
}
|
||||
|
||||
// AddDeployKey add new deploy key to database and authorized_keys file.
|
||||
func AddDeployKey(repoID int64, name, content string) (err error) {
|
||||
if err = checkKeyContent(content); err != nil {
|
||||
return err
|
||||
func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
|
||||
if err := checkKeyContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := &PublicKey{
|
||||
pkey := &PublicKey{
|
||||
Content: content,
|
||||
Mode: ACCESS_MODE_READ,
|
||||
Type: KEY_TYPE_DEPLOY,
|
||||
}
|
||||
has, err := x.Get(key)
|
||||
has, err := x.Get(pkey)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First time use this deploy key.
|
||||
if !has {
|
||||
if err = addKey(sess, key); err != nil {
|
||||
return nil
|
||||
if err = addKey(sess, pkey); err != nil {
|
||||
return nil, fmt.Errorf("addKey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = addDeployKey(sess, key.ID, repoID, name, key.Fingerprint); err != nil {
|
||||
return err
|
||||
key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("addDeployKey: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
return key, sess.Commit()
|
||||
}
|
||||
|
||||
// GetDeployKeyByID returns deploy key by given ID.
|
||||
func GetDeployKeyByID(id int64) (*DeployKey, error) {
|
||||
key := new(DeployKey)
|
||||
has, err := x.Id(id).Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrDeployKeyNotExist{id, 0, 0}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
|
||||
@@ -636,8 +640,13 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
|
||||
KeyID: keyID,
|
||||
RepoID: repoID,
|
||||
}
|
||||
_, err := x.Get(key)
|
||||
return key, err
|
||||
has, err := x.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrDeployKeyNotExist{0, keyID, repoID}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// UpdateDeployKey updates deploy key information.
|
||||
|
||||
+50
-16
@@ -166,43 +166,49 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
|
||||
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecTimeout(5*time.Minute,
|
||||
fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
|
||||
fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
|
||||
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
|
||||
return fmt.Errorf("git clone: %s", stderr)
|
||||
}
|
||||
|
||||
// Check out base branch.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
|
||||
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
|
||||
"git", "checkout", pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
// Add head repo remote.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git remote add): %s", tmpBasePath),
|
||||
fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
|
||||
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
|
||||
return fmt.Errorf("git remote add[%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
// Merge commits.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git fetch): %s", tmpBasePath),
|
||||
fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
|
||||
"git", "fetch", "head_repo"); err != nil {
|
||||
return fmt.Errorf("git fetch[%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git merge): %s", tmpBasePath),
|
||||
"git", "merge", "--no-ff", "-m",
|
||||
fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch),
|
||||
"head_repo/"+pr.HeadBranch); err != nil {
|
||||
return fmt.Errorf("git merge[%s]: %s", tmpBasePath, stderr)
|
||||
fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
|
||||
"git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil {
|
||||
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
|
||||
}
|
||||
|
||||
sig := doer.NewGitSig()
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
|
||||
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch)); err != nil {
|
||||
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
|
||||
}
|
||||
|
||||
// Push back to upstream.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
|
||||
fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
|
||||
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
@@ -218,6 +224,7 @@ var patchConflicts = []string{
|
||||
}
|
||||
|
||||
// testPatch checks if patch can be merged to base repository without conflit.
|
||||
// FIXME: make a mechanism to clean up stable local copies.
|
||||
func (pr *PullRequest) testPatch() (err error) {
|
||||
if pr.BaseRepo == nil {
|
||||
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
|
||||
@@ -243,14 +250,23 @@ func (pr *PullRequest) testPatch() (err error) {
|
||||
return fmt.Errorf("UpdateLocalCopy: %v", err)
|
||||
}
|
||||
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
// Checkout base branch.
|
||||
_, stderr, err := process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
|
||||
fmt.Sprintf("PullRequest.Merge(git checkout): %s", pr.BaseRepo.ID),
|
||||
"git", "checkout", pr.BaseBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
_, stderr, err = process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
|
||||
fmt.Sprintf("testPatch(git apply --check): %d", pr.BaseRepo.ID),
|
||||
"git", "apply", "--check", patchPath)
|
||||
if err != nil {
|
||||
for i := range patchConflicts {
|
||||
if strings.Contains(stderr, patchConflicts[i]) {
|
||||
log.Trace("PullRequest[%d].testPatch(apply): has conflit", pr.ID)
|
||||
fmt.Println(stderr)
|
||||
pr.Status = PULL_REQUEST_STATUS_CONFLICT
|
||||
return nil
|
||||
}
|
||||
@@ -385,16 +401,18 @@ func (pr *PullRequest) UpdateCols(cols ...string) error {
|
||||
var PullRequestQueue = NewUniqueQueue(setting.Repository.PullRequestQueueLength)
|
||||
|
||||
// UpdatePatch generates and saves a new patch.
|
||||
func (pr *PullRequest) UpdatePatch() error {
|
||||
if err := pr.GetHeadRepo(); err != nil {
|
||||
func (pr *PullRequest) UpdatePatch() (err error) {
|
||||
if err = pr.GetHeadRepo(); err != nil {
|
||||
return fmt.Errorf("GetHeadRepo: %v", err)
|
||||
} else if pr.HeadRepo == nil {
|
||||
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pr.GetBaseRepo(); err != nil {
|
||||
if err = pr.GetBaseRepo(); err != nil {
|
||||
return fmt.Errorf("GetBaseRepo: %v", err)
|
||||
} else if err = pr.BaseRepo.GetOwner(); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
headRepoPath, err := pr.HeadRepo.RepoPath()
|
||||
@@ -407,6 +425,22 @@ func (pr *PullRequest) UpdatePatch() error {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.Owner.Name, pr.BaseRepo.Name)); err != nil {
|
||||
return fmt.Errorf("AddRemote: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
headGitRepo.RemoveRemote(tmpRemote)
|
||||
}()
|
||||
remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
|
||||
pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetMergeBase: %v", err)
|
||||
} else if err = pr.Update(); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
|
||||
patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetPatch: %v", err)
|
||||
|
||||
+61
-22
@@ -5,7 +5,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -13,18 +13,14 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReleaseAlreadyExist = errors.New("Release already exist")
|
||||
ErrReleaseNotExist = errors.New("Release does not exist")
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
)
|
||||
|
||||
// Release represents a release of repository.
|
||||
type Release struct {
|
||||
Id int64
|
||||
RepoId int64
|
||||
PublisherId int64
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64
|
||||
PublisherID int64
|
||||
Publisher *User `xorm:"-"`
|
||||
TagName string
|
||||
LowerTagName string
|
||||
@@ -47,12 +43,12 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
|
||||
// IsReleaseExist returns true if release with given tag name already exists.
|
||||
func IsReleaseExist(repoId int64, tagName string) (bool, error) {
|
||||
func IsReleaseExist(repoID int64, tagName string) (bool, error) {
|
||||
if len(tagName) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return x.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
|
||||
return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
|
||||
}
|
||||
|
||||
func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
@@ -64,7 +60,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.Id.String()); err != nil {
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -84,11 +80,11 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
|
||||
// CreateRelease creates a new release of repository.
|
||||
func CreateRelease(gitRepo *git.Repository, rel *Release) error {
|
||||
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
|
||||
isExist, err := IsReleaseExist(rel.RepoID, rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isExist {
|
||||
return ErrReleaseAlreadyExist
|
||||
return ErrReleaseAlreadyExist{rel.TagName}
|
||||
}
|
||||
|
||||
if err = createTag(gitRepo, rel); err != nil {
|
||||
@@ -100,22 +96,35 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
|
||||
}
|
||||
|
||||
// GetRelease returns release by given ID.
|
||||
func GetRelease(repoId int64, tagName string) (*Release, error) {
|
||||
isExist, err := IsReleaseExist(repoId, tagName)
|
||||
func GetRelease(repoID int64, tagName string) (*Release, error) {
|
||||
isExist, err := IsReleaseExist(repoID, tagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !isExist {
|
||||
return nil, ErrReleaseNotExist
|
||||
return nil, ErrReleaseNotExist{0, tagName}
|
||||
}
|
||||
|
||||
rel := &Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}
|
||||
rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
|
||||
_, err = x.Get(rel)
|
||||
return rel, err
|
||||
}
|
||||
|
||||
// GetReleasesByRepoId returns a list of releases of repository.
|
||||
func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
|
||||
err = x.Desc("created").Find(&rels, Release{RepoId: repoId})
|
||||
// GetReleaseByID returns release with given ID.
|
||||
func GetReleaseByID(id int64) (*Release, error) {
|
||||
rel := new(Release)
|
||||
has, err := x.Id(id).Get(rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrReleaseNotExist{id, ""}
|
||||
}
|
||||
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// GetReleasesByRepoID returns a list of releases of repository.
|
||||
func GetReleasesByRepoID(repoID int64) (rels []*Release, err error) {
|
||||
err = x.Desc("created").Find(&rels, Release{RepoID: repoID})
|
||||
return rels, err
|
||||
}
|
||||
|
||||
@@ -150,6 +159,36 @@ func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
|
||||
if err = createTag(gitRepo, rel); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Id(rel.Id).AllCols().Update(rel)
|
||||
_, err = x.Id(rel.ID).AllCols().Update(rel)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
|
||||
func DeleteReleaseByID(id int64) error {
|
||||
rel, err := GetReleaseByID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetReleaseByID: %v", err)
|
||||
}
|
||||
|
||||
repo, err := GetRepositoryByID(rel.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %v", err)
|
||||
}
|
||||
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("RepoPath: %v", err)
|
||||
}
|
||||
|
||||
_, stderr, err := process.ExecDir(-1, repoPath, fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID),
|
||||
"git", "tag", "-d", rel.TagName)
|
||||
if err != nil && !strings.Contains(stderr, "not found") {
|
||||
return fmt.Errorf("git tag -d: %v - %s", err, stderr)
|
||||
}
|
||||
|
||||
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+139
-58
@@ -17,12 +17,14 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bindata"
|
||||
@@ -48,7 +50,7 @@ var (
|
||||
Gitignores, Licenses, Readmes []string
|
||||
|
||||
// Maximum items per page in forks, watchers and stars of a repo
|
||||
ItemsPerPage = 54
|
||||
ItemsPerPage = 40
|
||||
)
|
||||
|
||||
func LoadRepoConfig() {
|
||||
@@ -319,7 +321,7 @@ func (repo *Repository) UpdateLocalCopy() error {
|
||||
}
|
||||
} else {
|
||||
_, stderr, err := process.ExecDir(-1, localPath,
|
||||
fmt.Sprintf("UpdateLocalCopy(git pull): %s", repoPath), "git", "pull")
|
||||
fmt.Sprintf("UpdateLocalCopy(git pull --all): %s", repoPath), "git", "pull", "--all")
|
||||
if err != nil {
|
||||
return fmt.Errorf("git pull: %v - %s", err, stderr)
|
||||
}
|
||||
@@ -379,11 +381,11 @@ func (repo *Repository) CloneLink() (cl CloneLink, err error) {
|
||||
}
|
||||
|
||||
if setting.SSHPort != 22 {
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.Name, repo.Name)
|
||||
} else {
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.Name, repo.Name)
|
||||
}
|
||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.Name, repo.Name)
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
@@ -537,6 +539,17 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
return repo, fmt.Errorf("create update hook: %v", err)
|
||||
}
|
||||
|
||||
// Clean up mirror info which prevents "push --all".
|
||||
configPath := filepath.Join(repoPath, "/config")
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("open config file: %v", err)
|
||||
}
|
||||
cfg.DeleteSection("remote \"origin\"")
|
||||
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
|
||||
return repo, fmt.Errorf("save config file: %v", err)
|
||||
}
|
||||
|
||||
// Check if repository is empty.
|
||||
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
if err != nil {
|
||||
@@ -563,20 +576,20 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath),
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath),
|
||||
"git", "add", "--all"); err != nil {
|
||||
return fmt.Errorf("git add: %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath),
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath),
|
||||
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", "initial commit"); err != nil {
|
||||
return fmt.Errorf("git commit: %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath),
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
|
||||
"git", "push", "origin", "master"); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
@@ -587,7 +600,7 @@ func createUpdateHook(repoPath string) error {
|
||||
hookPath := path.Join(repoPath, "hooks/update")
|
||||
os.MkdirAll(path.Dir(hookPath), os.ModePerm)
|
||||
return ioutil.WriteFile(hookPath,
|
||||
[]byte(fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+appPath+"\"", setting.CustomConf)), 0777)
|
||||
[]byte(fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf)), 0777)
|
||||
}
|
||||
|
||||
type CreateRepoOptions struct {
|
||||
@@ -687,7 +700,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
|
||||
// Init bare new repository.
|
||||
os.MkdirAll(repoPath, os.ModePerm)
|
||||
_, stderr, err := process.ExecDir(-1, repoPath,
|
||||
fmt.Sprintf("initRepository(git init --bare): %s", repoPath), "git", "init", "--bare")
|
||||
fmt.Sprintf("initRepository (git init --bare): %s", repoPath), "git", "init", "--bare")
|
||||
if err != nil {
|
||||
return fmt.Errorf("git init --bare: %v - %s", err, stderr)
|
||||
}
|
||||
@@ -898,9 +911,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
}
|
||||
|
||||
// Remove redundant collaborators.
|
||||
collaborators, err := repo.GetCollaborators()
|
||||
collaborators, err := repo.getCollaborators(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCollaborators: %v", err)
|
||||
return fmt.Errorf("getCollaborators: %v", err)
|
||||
}
|
||||
|
||||
// Dummy object.
|
||||
@@ -936,9 +949,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
}
|
||||
|
||||
if newOwner.IsOrganization() {
|
||||
t, err := newOwner.GetOwnerTeam()
|
||||
t, err := newOwner.getOwnerTeam(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetOwnerTeam: %v", err)
|
||||
return fmt.Errorf("getOwnerTeam: %v", err)
|
||||
} else if err = t.addRepository(sess, repo); err != nil {
|
||||
return fmt.Errorf("add to owner team: %v", err)
|
||||
}
|
||||
@@ -1104,7 +1117,7 @@ func DeleteRepository(uid, repoID int64) error {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Milestone{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil {
|
||||
} else if _, err = sess.Delete(&Release{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
@@ -1304,7 +1317,7 @@ func DeleteRepositoryArchives() error {
|
||||
repo := bean.(*Repository)
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepositoryArchives[%d]: %v", repo.ID, err)); err2 != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepositoryArchives.RepoPath [%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
@@ -1313,32 +1326,110 @@ func DeleteRepositoryArchives() error {
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMissingRepositories deletes all repository records that lost Git files.
|
||||
func DeleteMissingRepositories() error {
|
||||
repos := make([]*Repository, 0, 5)
|
||||
if err := x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepositoryArchives.RepoPath [%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !com.IsDir(repoPath) {
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteMissingRepositories: %v", err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
||||
if err := DeleteRepository(repo.OwnerID, repo.ID); err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewriteRepositoryUpdateHook rewrites all repositories' update hook.
|
||||
func RewriteRepositoryUpdateHook() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return err
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("RewriteRepositoryUpdateHook[%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return createUpdateHook(RepoPath(repo.Owner.Name, repo.Name))
|
||||
return createUpdateHook(repoPath)
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
// Prevent duplicate running tasks.
|
||||
isMirrorUpdating = false
|
||||
isGitFscking = false
|
||||
isCheckingRepos = false
|
||||
// statusPool represents a pool of status with true/false.
|
||||
type statusPool struct {
|
||||
lock sync.RWMutex
|
||||
pool map[string]bool
|
||||
}
|
||||
|
||||
// Start sets value of given name to true in the pool.
|
||||
func (p *statusPool) Start(name string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.pool[name] = true
|
||||
}
|
||||
|
||||
// Stop sets value of given name to false in the pool.
|
||||
func (p *statusPool) Stop(name string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.pool[name] = false
|
||||
}
|
||||
|
||||
// IsRunning checks if value of given name is set to true in the pool.
|
||||
func (p *statusPool) IsRunning(name string) bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.pool[name]
|
||||
}
|
||||
|
||||
// Prevent duplicate running tasks.
|
||||
var taskStatusPool = &statusPool{
|
||||
pool: make(map[string]bool),
|
||||
}
|
||||
|
||||
const (
|
||||
_MIRROR_UPDATE = "mirror_update"
|
||||
_GIT_FSCK = "git_fsck"
|
||||
_CHECK_REPOs = "check_repos"
|
||||
)
|
||||
|
||||
// MirrorUpdate checks and updates mirror repositories.
|
||||
func MirrorUpdate() {
|
||||
if isMirrorUpdating {
|
||||
if taskStatusPool.IsRunning(_MIRROR_UPDATE) {
|
||||
return
|
||||
}
|
||||
isMirrorUpdating = true
|
||||
defer func() { isMirrorUpdating = false }()
|
||||
taskStatusPool.Start(_MIRROR_UPDATE)
|
||||
defer taskStatusPool.Stop(_MIRROR_UPDATE)
|
||||
|
||||
log.Trace("Doing: MirrorUpdate")
|
||||
|
||||
@@ -1386,11 +1477,11 @@ func MirrorUpdate() {
|
||||
|
||||
// GitFsck calls 'git fsck' to check repository health.
|
||||
func GitFsck() {
|
||||
if isGitFscking {
|
||||
if taskStatusPool.IsRunning(_GIT_FSCK) {
|
||||
return
|
||||
}
|
||||
isGitFscking = true
|
||||
defer func() { isGitFscking = false }()
|
||||
taskStatusPool.Start(_GIT_FSCK)
|
||||
defer taskStatusPool.Stop(_GIT_FSCK)
|
||||
|
||||
log.Trace("Doing: GitFsck")
|
||||
|
||||
@@ -1455,11 +1546,11 @@ func repoStatsCheck(checker *repoChecker) {
|
||||
}
|
||||
|
||||
func CheckRepoStats() {
|
||||
if isCheckingRepos {
|
||||
if taskStatusPool.IsRunning(_CHECK_REPOs) {
|
||||
return
|
||||
}
|
||||
isCheckingRepos = true
|
||||
defer func() { isCheckingRepos = false }()
|
||||
taskStatusPool.Start(_CHECK_REPOs)
|
||||
defer taskStatusPool.Stop(_CHECK_REPOs)
|
||||
|
||||
log.Trace("Doing: CheckRepoStats")
|
||||
|
||||
@@ -1680,25 +1771,21 @@ func WatchRepo(uid, repoId int64, watch bool) (err error) {
|
||||
return watchRepo(x, uid, repoId, watch)
|
||||
}
|
||||
|
||||
func getWatchers(e Engine, rid int64) ([]*Watch, error) {
|
||||
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
|
||||
watches := make([]*Watch, 0, 10)
|
||||
err := e.Find(&watches, &Watch{RepoID: rid})
|
||||
return watches, err
|
||||
return watches, e.Find(&watches, &Watch{RepoID: repoID})
|
||||
}
|
||||
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
func GetWatchers(rid int64) ([]*Watch, error) {
|
||||
return getWatchers(x, rid)
|
||||
func GetWatchers(repoID int64) ([]*Watch, error) {
|
||||
return getWatchers(x, repoID)
|
||||
}
|
||||
|
||||
// Repository.GetWatchers returns all users watching given repository.
|
||||
func (repo *Repository) GetWatchers(offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, 10)
|
||||
offset = (offset - 1) * ItemsPerPage
|
||||
|
||||
err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
|
||||
|
||||
return users, err
|
||||
// Repository.GetWatchers returns range of users watching given repository.
|
||||
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
|
||||
users := make([]*User, 0, ItemsPerPage)
|
||||
return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
||||
Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
|
||||
}
|
||||
|
||||
func notifyWatchers(e Engine, act *Action) error {
|
||||
@@ -1778,13 +1865,10 @@ func IsStaring(uid, repoId int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func (repo *Repository) GetStars(offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, 10)
|
||||
offset = (offset - 1) * ItemsPerPage
|
||||
|
||||
err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
|
||||
|
||||
return users, err
|
||||
func (repo *Repository) GetStargazers(page int) ([]*User, error) {
|
||||
users := make([]*User, 0, ItemsPerPage)
|
||||
return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
||||
Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
|
||||
}
|
||||
|
||||
// ___________ __
|
||||
@@ -1856,9 +1940,6 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
|
||||
}
|
||||
|
||||
func (repo *Repository) GetForks() ([]*Repository, error) {
|
||||
forks := make([]*Repository, 0, 10)
|
||||
|
||||
err := x.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
|
||||
return forks, err
|
||||
forks := make([]*Repository, 0, repo.NumForks)
|
||||
return forks, x.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
}
|
||||
+20
-20
@@ -10,7 +10,6 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
@@ -28,6 +27,7 @@ func AddUpdateTask(task *UpdateTask) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetUpdateTaskByUUID returns update task by given UUID.
|
||||
func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
|
||||
task := &UpdateTask{
|
||||
UUID: uuid,
|
||||
@@ -36,7 +36,7 @@ func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("task does not exist: %s", uuid)
|
||||
return nil, ErrUpdateTaskNotExist{uuid}
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
@@ -46,10 +46,10 @@ func DeleteUpdateTaskByUUID(uuid string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitId, "0000000")
|
||||
func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName string, userID int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitID, "0000000")
|
||||
if isNew &&
|
||||
strings.HasPrefix(newCommitId, "0000000") {
|
||||
strings.HasPrefix(newCommitID, "0000000") {
|
||||
return fmt.Errorf("old rev and new rev both 000000")
|
||||
}
|
||||
|
||||
@@ -59,23 +59,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
gitUpdate.Dir = f
|
||||
gitUpdate.Run()
|
||||
|
||||
isDel := strings.HasPrefix(newCommitId, "0000000")
|
||||
isDel := strings.HasPrefix(newCommitID, "0000000")
|
||||
if isDel {
|
||||
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
|
||||
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
repo, err := git.OpenRepository(f)
|
||||
gitRepo, err := git.OpenRepository(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.Open repoId: %v", err)
|
||||
}
|
||||
|
||||
ru, err := GetUserByName(repoUserName)
|
||||
user, err := GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.GetUserByName: %v", err)
|
||||
}
|
||||
|
||||
repos, err := GetRepositoryByName(ru.Id, repoName)
|
||||
repo, err := GetRepositoryByName(user.Id, repoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
// Push tags.
|
||||
if strings.HasPrefix(refName, "refs/tags/") {
|
||||
tagName := git.RefEndName(refName)
|
||||
tag, err := repo.GetTag(tagName)
|
||||
tag, err := gitRepo.GetTag(tagName)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(4, "runUpdate.GetTag: %v", err)
|
||||
}
|
||||
@@ -99,16 +99,16 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
actEmail = cmt.Committer.Email
|
||||
}
|
||||
|
||||
commit := &base.PushCommits{}
|
||||
commit := &PushCommits{}
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.ID, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, commit, oldCommitID, newCommitID); err != nil {
|
||||
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
newCommit, err := repo.GetCommit(newCommitId)
|
||||
newCommit, err := gitRepo.GetCommit(newCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
return fmt.Errorf("CommitsBefore: %v", err)
|
||||
}
|
||||
} else {
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitId)
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsBeforeUntil: %v", err)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
}
|
||||
|
||||
// Push commits.
|
||||
commits := make([]*base.PushCommit, 0)
|
||||
commits := make([]*PushCommit, 0)
|
||||
var actEmail string
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
commit := e.Value.(*git.Commit)
|
||||
@@ -140,15 +140,15 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
actEmail = commit.Committer.Email
|
||||
}
|
||||
commits = append(commits,
|
||||
&base.PushCommit{commit.Id.String(),
|
||||
&PushCommit{commit.ID.String(),
|
||||
commit.Message(),
|
||||
commit.Author.Email,
|
||||
commit.Author.Name,
|
||||
})
|
||||
}
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, &PushCommits{l.Len(), commits, "", nil}, oldCommitID, newCommitID); err != nil {
|
||||
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
+41
-15
@@ -14,6 +14,7 @@ import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -75,9 +76,10 @@ type User struct {
|
||||
LastRepoVisibility bool
|
||||
|
||||
// Permissions.
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
AllowImportLocal bool // Allow migrate repository by local path
|
||||
|
||||
// Avatar.
|
||||
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
|
||||
@@ -107,6 +109,22 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// HasForkedRepo checks if user has already forked a repository with given ID.
|
||||
func (u *User) HasForkedRepo(repoID int64) bool {
|
||||
_, has := HasForkedRepo(u.Id, repoID)
|
||||
return has
|
||||
}
|
||||
|
||||
// CanEditGitHook returns true if user can edit Git hooks.
|
||||
func (u *User) CanEditGitHook() bool {
|
||||
return u.IsAdmin || u.AllowGitHook
|
||||
}
|
||||
|
||||
// CanImportLocal returns true if user can migrate repository by local path.
|
||||
func (u *User) CanImportLocal() bool {
|
||||
return u.IsAdmin || u.AllowImportLocal
|
||||
}
|
||||
|
||||
// EmailAdresses is the list of all email addresses of a user. Can contain the
|
||||
// primary email address, but is not obligatory
|
||||
type EmailAddress struct {
|
||||
@@ -242,14 +260,12 @@ func (u *User) ValidatePassword(passwd string) bool {
|
||||
// UploadAvatar saves custom avatar for user.
|
||||
// FIXME: split uploads to different subdirs in case we have massive users.
|
||||
func (u *User) UploadAvatar(data []byte) error {
|
||||
u.UseCustomAvatar = true
|
||||
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
|
||||
m := resize.Resize(234, 234, img, resize.NearestNeighbor)
|
||||
m := resize.Resize(290, 290, img, resize.NearestNeighbor)
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
@@ -257,19 +273,20 @@ func (u *User) UploadAvatar(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil {
|
||||
return err
|
||||
u.UseCustomAvatar = true
|
||||
if err = updateUser(sess, u); err != nil {
|
||||
return fmt.Errorf("updateUser: %v", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
|
||||
fw, err := os.Create(u.CustomAvatarPath())
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, m, nil); err != nil {
|
||||
return err
|
||||
if err = png.Encode(fw, m); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
@@ -356,6 +373,15 @@ func (u *User) DisplayName() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
// ShortName returns shorted user name with given maximum length,
|
||||
// it adds "..." at the end if user name has more length than maximum.
|
||||
func (u *User) ShortName(length int) string {
|
||||
if len(u.Name) < length {
|
||||
return u.Name
|
||||
}
|
||||
return u.Name[:length] + "..."
|
||||
}
|
||||
|
||||
// IsUserExist checks if given user name exist,
|
||||
// the user name should be noncased unique.
|
||||
// If uid is presented, then check will rule out that one,
|
||||
@@ -717,9 +743,9 @@ func UserPath(userName string) string {
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
func GetUserByKeyID(keyID int64) (*User, error) {
|
||||
user := new(User)
|
||||
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user)
|
||||
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyID).Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
@@ -980,7 +1006,7 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
return GetUserByID(emailAddress.UID)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{0, "email"}
|
||||
return nil, ErrUserNotExist{0, email}
|
||||
}
|
||||
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
|
||||
+2
-2
@@ -178,8 +178,8 @@ func GetActiveWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// GetWebhooksByRepoId returns all webhooks of repository.
|
||||
func GetWebhooksByRepoId(repoID int64) (ws []*Webhook, err error) {
|
||||
// GetWebhooksByRepoID returns all webhooks of repository.
|
||||
func GetWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
|
||||
err = x.Find(&ws, &Webhook{RepoID: repoID})
|
||||
return ws, err
|
||||
}
|
||||
|
||||
+11
-10
@@ -24,16 +24,17 @@ func (f *AdminCrateUserForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
||||
}
|
||||
|
||||
type AdminEditUserForm struct {
|
||||
LoginType string `binding:"Required"`
|
||||
LoginName string
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
LoginType string `binding:"Required"`
|
||||
LoginName string
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
AllowImportLocal bool
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -130,7 +130,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
|
||||
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
|
||||
log.Error(4, "LDAP Connect error (%s): %v", ls.Host, err)
|
||||
ls.Enabled = false
|
||||
return "", "", "", false, false
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ import (
|
||||
// \/ /_____/ \/ \/ \/ \/ \/
|
||||
|
||||
type CreateOrgForm struct {
|
||||
OrgName string `binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
|
||||
}
|
||||
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -25,7 +25,7 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
|
||||
}
|
||||
|
||||
type UpdateOrgSettingForm struct {
|
||||
Name string `binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
Name string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
)
|
||||
|
||||
// _______________________________________ _________.______________________ _______________.___.
|
||||
@@ -37,8 +43,8 @@ type MigrateRepoForm struct {
|
||||
AuthPassword string `json:"auth_password"`
|
||||
Uid int64 `json:"uid" binding:"Required"`
|
||||
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Private bool `json:"mirror"`
|
||||
Mirror bool `json:"private"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Private bool `json:"private"`
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
}
|
||||
|
||||
@@ -46,6 +52,34 @@ func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ParseRemoteAddr checks if given remote address is valid,
|
||||
// and returns composed URL with needed username and passowrd.
|
||||
// It also checks if given user has permission when remote address
|
||||
// is actually a local path.
|
||||
func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
|
||||
remoteAddr := f.CloneAddr
|
||||
|
||||
// Remote address can be HTTP/HTTPS/Git URL or local path.
|
||||
if strings.HasPrefix(remoteAddr, "http://") ||
|
||||
strings.HasPrefix(remoteAddr, "https://") ||
|
||||
strings.HasPrefix(remoteAddr, "git://") {
|
||||
u, err := url.Parse(remoteAddr)
|
||||
if err != nil {
|
||||
return "", models.ErrInvalidCloneAddr{IsURLError: true}
|
||||
}
|
||||
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
|
||||
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
} else if !user.CanImportLocal() {
|
||||
return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
|
||||
} else if !com.IsDir(remoteAddr) {
|
||||
return "", models.ErrInvalidCloneAddr{IsInvalidPath: true}
|
||||
}
|
||||
|
||||
return remoteAddr, nil
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
@@ -181,12 +215,12 @@ func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type NewReleaseForm struct {
|
||||
TagName string `form:"tag_name" binding:"Required"`
|
||||
TagName string `binding:"Required"`
|
||||
Target string `form:"tag_target" binding:"Required"`
|
||||
Title string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
Draft string `form:"draft"`
|
||||
Prerelease bool `form:"prerelease"`
|
||||
Title string `binding:"Required"`
|
||||
Content string
|
||||
Draft string
|
||||
Prerelease bool
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -39,6 +39,8 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
//FIXME: remove cache module
|
||||
|
||||
var gravatarSource string
|
||||
|
||||
func UpdateGravatarSource() {
|
||||
@@ -102,7 +104,7 @@ func New(hash string, cacheDir string) *Avatar {
|
||||
expireDuration: time.Minute * 10,
|
||||
reqParams: url.Values{
|
||||
"d": {"retro"},
|
||||
"size": {"200"},
|
||||
"size": {"290"},
|
||||
"r": {"pg"}}.Encode(),
|
||||
imagePath: filepath.Join(cacheDir, hash+".image"), //maybe png or jpeg
|
||||
}
|
||||
@@ -153,7 +155,7 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) {
|
||||
if img, err = decodeImageFile(imgPath); err != nil {
|
||||
return
|
||||
}
|
||||
m := resize.Resize(uint(size), 0, img, resize.NearestNeighbor)
|
||||
m := resize.Resize(uint(size), 0, img, resize.Lanczos3)
|
||||
return jpeg.Encode(wr, m, nil)
|
||||
}
|
||||
|
||||
@@ -192,7 +194,7 @@ func (this *service) mustInt(r *http.Request, defaultValue int, keys ...string)
|
||||
func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
urlPath := r.URL.Path
|
||||
hash := urlPath[strings.LastIndex(urlPath, "/")+1:]
|
||||
size := this.mustInt(r, 80, "s", "size") // default size = 80*80
|
||||
size := this.mustInt(r, 290, "s", "size") // default size = 290*290
|
||||
|
||||
avatar := New(hash, this.cacheDir)
|
||||
avatar.AlterImage = this.altImage
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const DOC_URL = "https://github.com/gogits/go-gogs-client/wiki"
|
||||
|
||||
type (
|
||||
@@ -11,3 +17,16 @@ type (
|
||||
)
|
||||
|
||||
var GoGetMetas = make(map[string]bool)
|
||||
|
||||
// ExecPath returns the executable path.
|
||||
func ExecPath() (string, error) {
|
||||
file, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/russross/blackfriday"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
@@ -99,12 +100,33 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
|
||||
options.Renderer.Link(out, link, title, content)
|
||||
}
|
||||
|
||||
var (
|
||||
svgSuffix = []byte(".svg")
|
||||
svgSuffixWithMark = []byte(".svg?")
|
||||
)
|
||||
|
||||
func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
link = []byte(path.Join(strings.Replace(options.urlPrefix, "/src/", "/raw/", 1), string(link)))
|
||||
prefix := strings.Replace(options.urlPrefix, "/src/", "/raw/", 1)
|
||||
if len(link) > 0 {
|
||||
if isLink(link) {
|
||||
// External link with .svg suffix usually means CI status.
|
||||
if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) {
|
||||
options.Renderer.Image(out, link, title, alt)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if link[0] != '/' {
|
||||
prefix += "/"
|
||||
}
|
||||
link = []byte(prefix + string(link))
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteString(`<a href="`)
|
||||
out.Write(link)
|
||||
out.WriteString(`">`)
|
||||
options.Renderer.Image(out, link, title, alt)
|
||||
out.WriteString("</a>")
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -159,7 +181,21 @@ func RenderSha1CurrentPattern(rawBytes []byte, urlPrefix string) []byte {
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
func cutoutVerbosePrefix(prefix string) string {
|
||||
count := 0
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if prefix[i] == '/' {
|
||||
count++
|
||||
}
|
||||
if count >= 3 {
|
||||
return prefix[:i]
|
||||
}
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string) []byte {
|
||||
urlPrefix = cutoutVerbosePrefix(urlPrefix)
|
||||
ms := issueIndexPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
var space string
|
||||
@@ -209,11 +245,21 @@ func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
|
||||
return body
|
||||
}
|
||||
|
||||
var (
|
||||
leftAngleBracket = []byte("</")
|
||||
rightAngleBracket = []byte(">")
|
||||
)
|
||||
|
||||
var noEndTags = []string{"img", "input", "br", "hr"}
|
||||
|
||||
// PostProcessMarkdown treats different types of HTML differently,
|
||||
// and only renders special links for plain text blocks.
|
||||
func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte {
|
||||
startTags := make([]string, 0, 5)
|
||||
var buf bytes.Buffer
|
||||
tokenizer := html.NewTokenizer(bytes.NewReader(rawHtml))
|
||||
|
||||
OUTER_LOOP:
|
||||
for html.ErrorToken != tokenizer.Next() {
|
||||
token := tokenizer.Token()
|
||||
switch token.Type {
|
||||
@@ -225,17 +271,38 @@ func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte {
|
||||
tagName := token.Data
|
||||
// If this is an excluded tag, we skip processing all output until a close tag is encountered.
|
||||
if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) {
|
||||
stackNum := 1
|
||||
for html.ErrorToken != tokenizer.Next() {
|
||||
token = tokenizer.Token()
|
||||
|
||||
// Copy the token to the output verbatim
|
||||
buf.WriteString(token.String())
|
||||
// If this is the close tag, we are done
|
||||
if html.EndTagToken == token.Type && strings.EqualFold(tagName, token.Data) {
|
||||
break
|
||||
|
||||
if token.Type == html.StartTagToken {
|
||||
stackNum++
|
||||
}
|
||||
|
||||
// If this is the close tag to the outer-most, we are done
|
||||
if token.Type == html.EndTagToken && strings.EqualFold(tagName, token.Data) {
|
||||
stackNum--
|
||||
|
||||
if stackNum == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
continue OUTER_LOOP
|
||||
}
|
||||
|
||||
if !com.IsSliceContainsStr(noEndTags, token.Data) {
|
||||
startTags = append(startTags, token.Data)
|
||||
}
|
||||
|
||||
case html.EndTagToken:
|
||||
buf.Write(leftAngleBracket)
|
||||
buf.WriteString(startTags[len(startTags)-1])
|
||||
buf.Write(rightAngleBracket)
|
||||
startTags = startTags[:len(startTags)-1]
|
||||
default:
|
||||
buf.WriteString(token.String())
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/Unknwon/i18n"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
|
||||
"github.com/gogits/chardet"
|
||||
|
||||
"github.com/gogits/gogs/modules/avatar"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
@@ -43,6 +45,22 @@ func EncodeSha1(str string) string {
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func ShortSha(sha1 string) string {
|
||||
if len(sha1) == 40 {
|
||||
return sha1[:10]
|
||||
}
|
||||
return sha1
|
||||
}
|
||||
|
||||
func DetectEncoding(content []byte) (string, error) {
|
||||
detector := chardet.NewTextDetector()
|
||||
result, err := detector.DetectBest(content)
|
||||
if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
|
||||
return setting.Repository.AnsiCharset, err
|
||||
}
|
||||
return result.Charset, err
|
||||
}
|
||||
|
||||
func BasicAuthDecode(encoded string) (string, string, error) {
|
||||
s, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
|
||||
+32
-32
@@ -284,7 +284,7 @@ func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _confAppIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x7a\x5d\x8f\xe3\x46\x76\xf6\x3d\x7f\x45\x59\x7e\xfd\x7a\x1c\x48\xea\x2f\xcf\x87\xdb\xee\xc4\x6a\x89\x52\x73\x47\x5f\x26\xa5\x19\x8f\x07\x0d\x36\x9b\x2c\x49\x74\x53\xa4\x86\x45\x76\xb7\x8c\x5c\xac\x91\x8b\x00\xb9\x4d\x90\xdc\x04\x41\x72\x11\x04\xd8\x7c\x20\x8b\xdc\xec\x26\xc8\x95\x91\xfb\x99\xff\xb0\xf0\x26\xff\x22\xcf\x39\x45\x4a\x54\x4f\xbb\x91\x05\x92\x5d\xa3\x45\x16\xab\x0e\xeb\x3c\x75\xce\x79\x9e\x2a\xce\x87\x62\x68\xbe\x30\x6d\xc1\x7f\x06\xa3\x8e\xd5\x7d\x25\x26\x67\x96\x23\xba\x56\xdf\x34\x3e\x14\xe3\xbe\xd9\x72\x4c\x31\x68\x3d\x37\x45\xfb\xac\x35\xec\x99\x8e\x18\x0d\x45\x7b\x64\xdb\xa6\x33\x1e\x0d\x3b\xd6\xb0\x27\xda\x53\x67\x32\x1a\xa0\x71\xd8\xb5\x7a\x7a\xa4\xf1\xb9\x68\xad\x56\x22\xf6\x96\x52\x64\x0b\x2f\x13\x6a\x91\xdc\x28\x91\xc4\x42\x5e\xcb\x74\x2d\x56\xde\x1c\x0f\xc2\x2c\x92\x46\x6b\x3c\x76\x87\xad\x81\x29\x4e\x44\x2f\x99\xab\x63\xfc\x15\xbd\x30\x13\x8e\x4c\xaf\x43\x5f\xc2\x52\x7b\xe1\xc5\xe8\x8e\xb6\x70\x26\xd6\x49\x2e\xd2\x3c\x16\x51\xe2\x7b\x51\xb4\x36\xec\xe9\xd0\x9d\x3a\x98\xfd\x89\x98\x87\x19\x7a\x9b\x61\xb6\x90\xa9\xa8\x05\xf2\xba\x56\x17\xb5\x55\x9a\x04\x35\x91\xa0\x21\x93\x2a\x43\x4b\x20\x67\x5e\x1e\xc1\x96\xd2\x7d\xd8\x02\x5c\xa7\x09\xe0\xde\x30\x5e\xa7\x72\x95\xa8\x30\x4b\xd2\xf5\xb9\x61\x8f\x46\x13\x71\x62\x38\x6d\xdb\x1a\x4f\xdc\xc9\xab\x31\x75\xbb\xf4\xd4\x02\x6f\xea\x14\x96\x5a\x43\xc7\x12\xfe\xc2\x4b\x95\xcc\x0c\xba\x71\x01\x95\xed\x98\x18\x28\xd0\xad\x9b\xa4\xbe\x2c\xfc\x8e\xe5\x8d\xd8\x9a\x17\x59\x22\x2e\xa5\x58\xa5\xe1\xb5\x97\x49\xa3\x3b\xb2\xdb\xa6\x3b\xb6\xad\x17\xad\x09\xbd\x66\xe6\x45\x8a\xfc\x1f\x7b\x99\xbf\x10\x34\x7d\xf1\x26\x97\xb9\x14\x91\x8c\xe7\xd9\xa2\x2e\x96\xde\x15\xc3\xe2\x29\x11\x79\x29\x20\xc2\x05\x6c\xab\xf0\x12\xb8\x8e\xa7\xfd\xbe\x6b\x9b\x5f\x4d\x4d\x67\xe2\xe2\xef\xd4\x74\xfb\xe6\xb0\x37\x39\x83\xe5\x83\x7d\xfc\x0f\x9e\xe6\xe1\x39\xec\x0f\xf3\xe5\x25\x10\x4b\x66\xdb\xa9\x85\x52\xe9\x75\xf3\x52\xc9\x6b\x27\x03\x11\xc6\x58\x3f\x38\x72\xbb\x8a\x12\xb4\xd2\x12\x1a\xe6\xd7\xe3\xfe\xc8\xc6\xa4\x5b\x3d\x44\x82\x3b\x9c\x0e\x60\xfd\x70\x7f\xc7\x68\xa8\x54\xfe\xd3\xe6\xd8\x8c\xe5\x38\xd3\x3b\x46\x0e\x76\x8d\x2c\xbd\xdb\x70\x99\x2f\x85\x9f\x2c\x97\x61\xa6\xee\x18\xf1\xfc\x2c\xbc\x0e\xb3\xb5\x98\x49\x19\x18\x5d\xd3\xec\xb8\x83\xd6\xd7\x6e\x7b\x34\x18\x58\x93\xc2\xe0\x63\xf6\xb7\xe9\x05\xcb\x30\xde\xf5\x3a\x57\x32\x7d\x78\x7e\x14\x60\xbb\xd3\x7b\xbc\xff\x3e\x70\x0f\x9a\xb0\xcd\xf1\xe8\x41\x13\x71\x92\x21\xdc\x1f\x36\x32\x1c\x4d\xac\xb6\xf9\xa0\x99\x24\x9d\x7b\x71\xf8\x9d\x97\x85\x48\xb6\x87\x6c\x8d\xec\xde\x7b\x86\x8c\xd7\x4b\x2f\xbd\x0a\x92\x1b\x46\xc8\x8c\x3d\x04\x92\x40\x60\x07\x22\x0a\x31\xee\x32\x95\xde\x15\x02\x20\x93\xb1\x82\x79\xc3\x1c\xb6\x4e\xfb\xa6\x8b\x60\xef\xb8\x7d\x6b\x68\xba\xa7\xb6\xd9\x7a\xbe\x89\x5c\xe3\x35\x80\x45\xd8\x9f\x1b\x63\x7b\x34\x19\xb5\x47\x7d\x3c\x5a\x64\xd9\xca\xe8\x8c\x06\x2d\x6b\x88\x3b\xce\xe2\x45\xa2\x32\x4e\x34\x77\x6a\x53\x97\x8f\x1e\x95\xfd\x3f\x51\xc7\x7b\x7b\x1f\x3d\xd2\xdd\x71\xf3\xd1\xa3\xb3\xc9\x64\xec\x8e\x47\xf6\xe4\x13\xb5\x67\xf0\x4d\xab\xd3\x41\xf2\x1b\x9b\x07\x30\x70\x44\xd1\x8d\xfc\x0c\x15\x3b\xe0\x38\x67\x08\x0c\x2f\xcb\x01\xc4\xcd\x42\xc6\x04\xb5\xf0\xae\xbd\x30\xa2\xc7\x46\xc7\x72\xd8\x0d\xea\x56\x4e\x1d\xd7\xa5\xb1\xc3\xc3\x8a\xa9\x76\x67\x48\x99\x1c\x13\x92\x45\x69\x59\x26\x01\xc0\xec\x76\x19\x80\xa2\x8e\x68\x23\xa5\x61\x7b\x34\x9d\x20\x7e\xfa\xa3\x5e\x25\xa9\x7b\x32\x96\x29\xb2\x5e\xa8\x4c\xae\xd4\x31\x5a\xfe\x9f\xf0\x03\x2c\x4d\xb6\xd8\xcb\x92\xbd\x39\x4a\xe1\x9e\x9f\xab\x2c\x59\xee\x11\x64\x8a\x3b\x34\xb9\x5d\xf8\x32\xcd\x44\xc3\xf7\x4e\xb2\x14\xd5\xa0\x11\xe4\x29\x2f\xf7\xc9\xb3\xa7\x4f\xf6\x17\xfb\xcb\x7d\x25\x1a\x84\xe9\xc9\x72\x4d\x3f\x4d\x79\xeb\x2d\x57\x91\x6c\x22\x77\x8c\xcf\x61\x67\x94\x8a\x59\x9a\x2c\x85\x27\x9a\xab\xd9\xad\x98\x85\x11\x67\x75\x92\x66\x08\x11\x7e\x82\x0a\x2a\x5e\x86\x71\x40\x35\x9b\x5e\x16\xce\x42\x5f\xcf\x95\x32\xff\x51\x90\xc0\x0a\x81\x38\x43\xb4\xc9\x8c\xca\x98\x1e\xcf\x03\x8b\x72\x26\xae\xe4\xfa\x13\xed\x57\xb2\x42\xc0\xa8\x48\xac\xae\x7c\x75\x70\x28\x1a\x00\x8f\xac\xf2\xdb\x1b\x49\x9e\x15\x77\x72\x29\x1a\x71\x82\x61\xea\x7f\x36\x0a\x3d\xcb\x41\xf4\x40\xd1\x45\x20\x95\xd1\x36\xed\x89\x4b\x34\x04\xb8\xab\x10\xee\x95\xaf\x31\x9e\x9b\xaf\xee\xed\x50\x58\xc4\xeb\xa7\xab\x15\xf2\x29\xc2\x5a\x47\x94\x55\x99\x04\x82\xe4\x94\x17\x07\x40\x01\x70\xfb\x1a\x37\x5a\x2f\x74\xaf\x90\x0a\x43\x80\x56\x0a\x35\x80\x45\x9c\x46\xcd\xf2\x56\xfa\x39\x00\x36\x9c\x49\x0b\x49\xec\x72\xbc\x8f\x5b\x54\x8e\x35\x59\x46\x04\x31\x25\xad\x7e\x69\xef\x1b\x6b\x2c\x54\xbe\x22\x58\xcb\x44\xe3\xb6\x6d\x08\xf5\x31\x99\x30\x9e\x6b\x32\xc5\x52\x60\x49\xe2\x46\x94\xcc\xe7\x58\x46\xae\x69\x75\xe1\x7b\x31\x31\x4c\x6d\x91\x2c\xa5\x66\xc1\xa2\x7c\xd7\x8c\x7e\x8b\xd9\x9b\x6a\x00\xe1\x40\x3d\x90\xb1\x81\x97\x79\xa0\x37\x79\x5e\x61\xd2\xe5\x5a\xbd\x89\x98\x4b\x11\x4d\xf3\x54\x2a\x6d\x09\x8d\x61\x26\x8f\xf0\x20\xcc\x3e\x56\x44\xcc\x29\x58\x30\x21\xce\xee\x9c\x96\x54\xc9\x63\x8d\xb3\x91\x43\xa9\x74\x70\xf8\xb4\xb9\x8f\xff\x1f\x1c\x1f\x1d\xed\x3f\x31\x0a\xd6\Line truncated
|
||||
var _confAppIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x7a\x5d\x8f\xe3\x46\x76\xf6\x3d\x7f\x45\x59\x7e\xfd\x7a\x1c\x48\xea\x2f\xf7\xcc\xb8\xed\x4e\xac\x96\x28\x35\x77\xf4\x65\x52\x9a\xf1\x78\xd0\xe0\xb0\xc9\x92\x44\x37\x45\x6a\x58\x64\x77\xcb\xc8\xc5\x1a\xb9\x08\x90\xdb\x04\xc9\x4d\x10\x24\x17\x41\x80\xcd\x07\xb2\xc8\xcd\x6e\x82\x5c\x19\xb9\x9f\xf9\x0f\x0b\x6f\xf2\x2f\xf2\x9c\x53\xa4\x44\xf5\xb4\x1b\x59\x20\xd9\x35\x46\x64\xb1\xea\x54\xd5\x73\xbe\x9e\x53\xd5\x1f\x8a\xa1\xf9\xdc\xb4\x05\xff\x33\x18\x75\xac\xee\x4b\x31\x39\xb7\x1c\xd1\xb5\xfa\xa6\xf1\xa1\x18\xf7\xcd\x96\x63\x8a\x41\xeb\x99\x29\xda\xe7\xad\x61\xcf\x74\xc4\x68\x28\xda\x23\xdb\x36\x9d\xf1\x68\xd8\xb1\x86\x3d\xd1\x9e\x3a\x93\xd1\x00\x8d\xc3\xae\xd5\xd3\x23\x8d\xcf\x45\x6b\xb5\x12\xb1\xb7\x94\x22\x5b\x78\x99\x50\x8b\xe4\x46\x89\x24\x16\xf2\x5a\xa6\x6b\xb1\xf2\xe6\xf8\x10\x66\x91\x34\x5a\xe3\xb1\x3b\x6c\x0d\x4c\x71\x2a\x7a\xc9\x5c\x9d\xe0\x5f\xd1\x0b\x33\xe1\xc8\xf4\x3a\xf4\x25\x24\xb5\x17\x5e\x8c\xee\x68\x0b\x67\x62\x9d\xe4\x22\xcd\x63\x11\x25\xbe\x17\x45\x6b\xc3\x9e\x0e\xdd\xa9\x83\xd5\x9f\x8a\x79\x98\xa1\xb7\x19\x66\x0b\x99\x8a\x5a\x20\xaf\x6b\x75\x51\x5b\xa5\x49\x50\x13\x09\x1a\x32\xa9\x32\xb4\x04\x72\xe6\xe5\x11\x64\x29\xdd\x87\x25\x60\xeb\xb4\x00\xbc\x1b\xc6\xab\x54\xae\x12\x15\x66\x49\xba\xbe\x30\xec\xd1\x68\x22\x4e\x0d\xa7\x6d\x5b\xe3\x89\x3b\x79\x39\xa6\x6e\x97\x9e\x5a\x60\xa6\x4e\x21\xa9\x35\x74\x2c\xe1\x2f\xbc\x54\xc9\xcc\xa0\x17\x17\x50\xd9\x8e\x89\x81\x02\xdd\xba\x49\xea\xcb\x62\xdf\xb1\xbc\x11\x5b\xf1\x22\x4b\xc4\xa5\x14\xab\x34\xbc\xf6\x32\x69\x74\x47\x76\xdb\x74\xc7\xb6\xf5\xbc\x35\xa1\x69\x66\x5e\xa4\x68\xff\x63\x2f\xf3\x17\x82\x96\x2f\xde\xe4\x32\x97\x22\x92\xf1\x3c\x5b\xd4\xc5\xd2\xbb\x62\x58\x3c\x25\x22\x2f\x05\x44\x78\x80\x6c\x15\x5e\x02\xd7\xf1\xb4\xdf\x77\x6d\xf3\xab\xa9\xe9\x4c\x5c\xfc\x3b\x35\xdd\xbe\x39\xec\x4d\xce\x21\xf9\x60\x1f\xff\xc3\x4e\xf3\xf0\x02\xf2\x87\xf9\xf2\x12\x88\x25\xb3\xed\xd2\x42\xa9\xb4\xde\xbc\x54\xb2\xee\x64\x20\xc2\x18\xfa\xc3\x46\x6e\x57\x51\x82\x56\x52\xa1\x61\x7e\x3d\xee\x8f\x6c\x2c\xba\xd5\x83\x25\xb8\xc3\xe9\x00\xd2\x0f\xf7\x77\x84\x86\x4a\xe5\x3f\x2d\x8e\xc5\x58\x8e\x33\xbd\x23\xe4\x60\x57\xc8\xd2\xbb\x0d\x97\xf9\x52\xf8\xc9\x72\x19\x66\xea\x8e\x10\xcf\xcf\xc2\xeb\x30\x5b\x8b\x99\x94\x81\xd1\x35\xcd\x8e\x3b\x68\x7d\xed\xb6\x47\x83\x81\x35\x29\x04\x1e\xf3\x7e\x9b\x5e\xb0\x0c\xe3\xdd\x5d\xe7\x4a\xa6\x0f\xaf\x8f\x0c\x6c\x77\x79\xc7\xfb\xef\x03\xf7\xa0\x08\xdb\x1c\x8f\x1e\x14\x11\x27\x19\xcc\xfd\x61\x21\xc3\xd1\xc4\x6a\x9b\x0f\x8a\x49\xd2\xb9\x17\x87\xdf\x79\x59\x08\x67\x7b\x48\xd6\xc8\xee\xbd\x27\xc8\x78\xb5\xf4\xd2\xab\x20\xb9\x61\x84\xcc\xd8\x83\x21\x09\x18\x76\x20\xa2\x10\xe3\x2e\x53\xe9\x5d\xc1\x00\x32\x19\x2b\x88\x37\xcc\x61\xeb\xac\x6f\xba\x30\xf6\x8e\xdb\xb7\x86\xa6\x7b\x66\x9b\xad\x67\x1b\xcb\x35\x5e\x01\x58\x98\xfd\x85\x31\xb6\x47\x93\x51\x7b\xd4\xc7\xa7\x45\x96\xad\x8c\xce\x68\xd0\xb2\x86\x78\x63\x2f\x5e\x24\x2a\x63\x47\x73\xa7\x36\x75\xf9\xe8\x51\xd9\xff\x13\x75\xb2\xb7\xf7\xd1\x23\xdd\x1d\x2f\x1f\x3d\x3a\x9f\x4c\xc6\xee\x78\x64\x4f\x3e\x51\x7b\x06\xbf\xb4\x3a\x1d\x38\xbf\xb1\xf9\x00\x01\x47\x64\xdd\xf0\xcf\x50\xf1\x06\x1c\xe7\x1c\x86\xe1\x65\x39\x80\xb8\x59\xc8\x98\xa0\x16\xde\xb5\x17\x46\xf4\xd9\xe8\x58\x0e\x6f\x83\xba\x6d\x9d\xee\xc5\x42\x72\x1c\x81\x71\x88\xcb\x3c\x8c\x32\x60\x47\x3d\xf4\x96\x28\xa0\x40\x4a\xd3\x70\x26\x2d\x7b\x42\x43\x5d\x98\xc8\x73\x0e\x43\x5a\x02\x35\x15\xcb\x39\x3c\xac\x2c\xa6\xdd\x19\x52\x2c\x88\x49\x17\x45\x70\x5a\x26\x01\xd4\xd1\xed\x32\x84\x45\x24\xd2\x42\xca\xa5\xd9\xa3\xe9\x04\x16\xd8\x1f\xf5\x2a\x2b\xec\xc9\x58\xa6\x88\x1b\x42\x65\x72\xa5\x4e\xd0\xf2\xff\x84\x1f\x40\xb9\xd9\x62\x2f\x4b\xf6\xe6\x08\xa6\x7b\x7e\xae\xb2\x64\xb9\x47\xa0\x2b\xee\xd0\xe4\x76\xe1\xcb\x34\x13\x0d\xdf\x3b\xcd\x52\xc4\x93\x46\x90\xa7\x6c\x30\xa7\x4f\x9f\x3c\xde\x5f\xec\x2f\xf7\x95\x68\x90\x56\x4e\x97\x6b\xfa\x69\xca\x5b\x6f\xb9\x8a\x64\x13\xde\x67\x7c\x0e\x39\xa3\x54\xcc\xd2\x64\x29\x3c\xd1\x5c\xcd\x6e\xc5\x2c\x8c\x38\x2e\x24\x69\x06\x23\xe3\x2f\xc0\x4e\xbc\x08\xe3\x80\xa2\x3e\x4d\x16\xce\x42\x5f\xaf\x95\x62\xc7\xa3\x20\x81\x14\x52\xc3\x0c\xf6\x2a\x33\x0a\x84\x7a\x3c\x0f\x2c\x02\xa2\xb8\x92\xeb\x4f\xf4\xbe\x92\x15\x4c\x4e\x45\x62\x75\xe5\xab\x83\x43\xd1\x00\x78\x24\x95\x67\x6f\x24\x79\x56\xbc\xc9\xa5\x68\xc4\x09\x86\xa9\xff\xd9\x28\xf4\x2c\x07\xd1\x07\x45\x0f\x81\x54\x46\xdb\x84\x52\x29\x91\x01\xee\x2a\x84\x7b\xe5\x34\xc6\x33\xf3\xe5\xbd\x1d\x0a\x89\x98\x7e\xba\x5a\xc1\x4e\x22\xe8\x3a\x22\xbf\xcc\x24\x10\xa4\x4d\x79\x71\x00\x14\x00\xb7\xaf\x71\x23\x7d\xa1\x7b\x25\x2d\x31\x04\x68\x25\x63\x05\x58\x94\x15\xa9\x59\xde\x4a\x3f\x07\xc0\x64\x73\x08\x03\x2e\x7b\xcc\xb8\x45\x01\x5d\xa7\xdb\x88\x20\x26\xb7\xd7\x93\xf6\xbe\xb1\xc6\x42\xe5\x2b\x82\xb5\x74\x55\x6e\xdb\x9a\x50\x1f\x8b\x09\xe3\xb9\x4e\xc7\x33\xb6\xe9\xb8\x11\x25\xf3\x39\xd4\xc8\x51\xb1\x2e\x7c\x2f\xa6\x1c\x55\x5b\x24\x4b\xa9\xf3\x68\x91\x00\x6a\x46\xbf\xc5\xf9\x9f\xa2\x08\xe1\x40\x3d\xe0\xf3\x81\x97\x79\x48\x90\xf2\xa2\x92\x8b\x97\x6b\xf5\x26\xe2\x6c\x0c\x6b\x9a\xa7\x52\x69\x49\x68\x0c\x33\x79\x84\x0f\x61\xf6\xb1\Line truncated
|
||||
|
||||
func confAppIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -299,7 +299,7 @@ func confAppIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/app.ini", size: 9420, mode: os.FileMode(420), modTime: time.Unix(1445760015, 0)}
|
||||
info := bindataFileInfo{name: "conf/app.ini", size: 9746, mode: os.FileMode(420), modTime: time.Unix(1447884920, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
@@ -4304,7 +4304,7 @@ func confLicenseMozillaPublicLicense20() (*asset, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleTranslators = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x54\x92\xcf\x6e\xd3\x40\x10\xc6\xef\x7e\x8a\x91\x7a\x66\x55\x21\x21\x81\x64\x59\x76\x83\x28\xa5\x49\x41\x4a\x11\xea\x71\x62\x8f\xbd\x83\xf7\x4f\xd8\x5d\xa7\x49\x6f\x3c\x09\xcf\xd0\x0b\xe2\xde\xf6\xbd\x18\x3b\x0d\x0d\x07\x5b\x33\x3f\xcf\xce\xcc\xf7\x79\x4f\xe0\x5a\x73\x84\x96\x0d\x81\xe1\x98\x22\xa0\x31\xf0\xe5\xeb\xd9\xfc\x62\x06\xec\x1a\xde\x70\x33\xa0\x89\xa0\x71\xc3\xae\x83\xda\xbb\x14\x78\x35\x24\x6a\xa6\x98\x5c\x82\xe4\x21\x69\x82\x14\xd0\x45\x83\x89\xbd\x53\xd9\x09\x7c\x0e\x0d\x05\xf0\x2d\x38\xb4\x04\x32\xc3\x12\x3a\x69\x61\x28\x46\x95\x65\x55\xcf\x9a\x83\x87\x9b\xea\xbc\x5a\x56\x97\x17\x90\xef\xb0\xeb\x76\x29\x51\x28\xad\xb7\x8c\x1d\xa9\xda\xdb\x22\xab\x0c\x6d\xd1\x8d\xbd\x96\x89\xd8\xe9\x87\xdf\xad\xc4\x79\x2f\xc3\xb1\x4f\xa5\xd9\xbe\x8a\xaa\xa1\x7f\x75\x81\x60\x81\x9d\xf3\x90\xe3\x01\x28\xbb\xb2\x65\x67\x91\xcd\xbe\xe3\x19\x86\xc7\xfb\xa7\x5f\x50\x85\x06\xe1\xe6\xf1\xde\x58\xbc\x93\x72\xc9\x76\x3c\xc6\x9d\x2c\x1c\x8e\x0f\xcc\x74\x10\x6b\xfc\x5a\xc3\x25\xc7\x96\x4c\x03\x79\x7d\x40\xaa\xdf\xa3\xe3\xfa\xf7\x22\x94\x0c\x2c\xd7\xc4\xb5\xa6\x90\x20\x6f\x26\x52\xc6\x03\x51\x6b\x53\x64\xe7\x81\x3a\x2f\xb2\xd0\x25\x37\x4a\xea\x1a\xda\x94\x86\x37\x34\xe9\xf9\x38\xb0\x65\x07\xdf\x50\x5c\xcf\x6f\xe5\xad\xed\xeb\xd3\xd3\x77\xa5\xf6\xe9\x79\x92\xfa\xbe\x2e\x32\x36\xbd\x68\xa5\x11\x15\xd9\xb5\xf6\x16\x23\x7c\x40\x37\x3a\x3d\xf5\xf4\x5d\x54\x69\xc2\x65\x7b\xc0\x0a\x53\x91\x3d\xfd\x1c\x7a\x8c\x77\xf0\x09\x1d\x5c\x31\x59\x1e\xcb\xcd\xc4\x4a\xb7\xcf\xa7\x2d\xe7\xd8\x06\xee\xa3\x7c\xdb\x07\xc7\x42\xe7\x43\x3d\xfe\x95\xb5\xd4\xdf\xc6\x9e\xc7\xf3\x75\x19\x5f\x80\x6a\x43\x91\x2d\xb8\x1b\xc4\x8d\x46\x2e\x18\xc2\x2c\x0c\xe2\xb5\x9d\x50\x69\xeb\xb0\x55\x56\xb4\x2e\x30\x48\xa3\x5a\xb3\x31\xe3\x16\x56\xd2\x32\x3e\xa7\x8a\x65\xd0\xc2\x07\xb9\x6a\xb0\x7c\xf8\x13\xc8\x45\x89\xf2\xde\xb0\x7d\xfb\x9f\xeb\x57\x98\x44\x4a\x65\x56\xc3\x8f\x81\x82\x3c\x90\xbb\x11\xe1\x0b\x79\x73\x54\xff\x37\x00\x00\xff\xff\x1c\xd1\x3b\x45\xfa\x02\x00\x00")
|
||||
var _confLocaleTranslators = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x6c\x54\xcd\x4e\x1b\x31\x10\xbe\xe7\x29\x46\xe2\x5c\x84\x5a\x55\x2d\xd2\x0a\x29\x40\xf9\x0f\x54\x4a\x4a\xc5\x71\xb2\xeb\x5d\x0f\xeb\x9f\x60\x7b\xf3\x77\xeb\x93\xf4\x19\xb8\x54\xbd\x03\xef\xd5\x99\xdd\x10\x50\x93\x03\xec\xf8\xf3\x37\x9e\xf1\x7c\x9f\xb3\x03\x23\x4d\x11\x4a\x32\x0a\x0c\xc5\x14\x01\x8d\x81\xef\x3f\x0e\xaf\xce\x8f\x80\x5c\x41\x53\x2a\x1a\x34\x11\x34\x4e\xc9\x55\x90\x7b\x97\x02\x8d\x9b\xa4\x8a\x36\x56\x2e\x41\xf2\x90\xb4\x82\x14\xd0\x45\x83\x89\xbc\xdb\xed\xed\xc0\x37\x21\x2a\x3e\x2f\x28\x3e\x88\x8f\x9d\x68\x1c\xab\x44\x39\x1a\xf0\xa1\x50\x61\xb7\xd7\xeb\xd7\xa4\x29\x78\xb8\xeb\x9f\xf6\x87\xfd\xcb\x73\xc8\x16\x58\x55\x8b\x94\x54\x80\xfe\x08\xac\xb7\x84\x95\x82\xe3\x9b\x11\x17\xb3\x07\xbd\xbe\x51\x73\x74\x9c\x0b\xc3\xa4\xc8\xe9\xa7\x3f\x25\xc7\x59\xcd\x8d\x60\x9d\x24\xc5\xcc\x3f\xc4\x96\x5f\xa8\x35\x9d\x1b\x18\x60\xe5\x3c\x64\xb8\x06\x84\x62\xc7\x56\x52\x2a\x8b\x64\xde\xd5\x90\xfd\x05\x5c\xab\x20\xf7\xcd\xb0\x5b\x32\xcf\x75\xc8\x8a\xd8\x7e\xc7\x81\xf9\x21\xe9\x86\xdb\xe5\xab\xbb\x05\x3a\xce\xe8\x00\xd9\xef\xea\xe0\xeb\xd6\x66\xb1\x43\x0c\xcf\x8f\x2f\xbf\xa1\x1f\x0a\x84\xbb\xe7\x47\x63\x71\x29\x27\x14\xb8\x20\x89\x2b\xb4\xdd\x28\xfe\xcb\x3b\xd2\x81\xc5\xf2\x13\x0d\x97\x14\x4b\x65\x0a\xc8\xf2\x35\x24\xac\x7a\x05\x6f\xa6\x1e\xa3\x23\x65\x60\x38\x51\x94\x6b\x15\x12\x64\x45\x87\x30\x35\xae\x41\xa1\x4f\x0c\xb3\x2d\xb1\x8c\x3c\x0e\x5f\xe1\x02\x32\xab\x84\x96\x63\x9a\x69\x9f\xfb\xa2\xbb\x5f\x85\x07\xbd\xd3\xa0\x2a\xcf\xb2\xa0\x4b\x4e\x24\xa9\x0a\x35\x6d\xf5\xa0\xa9\x5a\xeb\x71\x86\x96\x0a\x38\x51\xb4\xc4\x31\x16\x04\x99\x16\xa0\x5c\x6e\x93\xe1\xac\x21\xcb\xb6\xf9\x89\x22\xc2\x8c\xff\x6b\xfb\x71\x6f\x6f\x5f\x98\xda\xa7\x77\xdc\xf6\x73\x3f\x39\xe8\x91\xa9\x7d\xef\x0a\xcb\x40\x75\x84\xcc\xac\x82\xcd\x93\xaf\xb0\x09\x04\x37\xf7\xec\x56\x9a\x36\x90\xcd\x85\x33\x57\xe9\x4b\x4b\xf1\xa1\x62\x4a\x93\x8b\xc3\x26\x3c\x97\x59\xac\xb9\x51\xc3\x80\xcc\xe7\x1d\x26\xe4\x92\xf5\x1f\x60\x60\x72\xae\xc9\x18\xb9\xb8\x95\xa5\x50\x5f\x11\xe1\x11\x97\x1d\x50\xd5\xf0\x94\x79\x66\x06\xe1\x28\x34\xac\xb3\xed\x20\x71\x7a\x1e\xe6\x9d\x27\x79\x4c\x03\x1f\xf8\x5d\xc1\xf0\xe9\x6f\x50\x2e\x72\x94\xd5\x86\xec\xd7\x6d\x62\x5e\x63\x12\x5f\x99\x71\xf3\xd0\xa8\xc0\x7f\x90\x39\x81\xf0\x0d\xf9\xbc\x25\xed\xa6\x20\xe3\x1d\x5c\x34\x8e\x58\xb4\xcc\x77\xcb\x76\x90\x2d\xb4\xff\x69\x4b\xd2\x48\x7b\x8b\x11\x4e\xd0\x39\x7e\x07\xad\xc8\xbe\xea\x1e\x5b\xea\xb6\x38\xa7\x5c\xef\xb6\xce\x4f\x9c\x26\x46\x76\x0e\x0e\x31\xd7\x90\xa5\xd5\x8a\xa9\xbe\x49\xc6\xfb\xfa\xad\xc0\xad\x61\x53\x58\x0a\x70\x4b\x31\xfa\xc6\x24\xb6\xe2\x94\xc5\x7f\x98\x29\xfe\x89\xd9\x6c\xe8\xee\x62\xe8\x4b\xf6\xef\xe2\x3e\xca\x97\x09\xab\xa8\x35\x6f\x67\xbb\x9a\x15\x7a\xf9\xd5\xd4\x18\x97\x70\xc1\xa3\xba\x26\x65\x49\x7a\x37\x1d\x26\xef\x7a\x05\xad\x2c\xff\x2f\x00\x00\xff\xff\x13\x05\xe1\x39\x12\x05\x00\x00")
|
||||
|
||||
func confLocaleTranslatorsBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4319,12 +4319,12 @@ func confLocaleTranslators() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/TRANSLATORS", size: 762, mode: os.FileMode(420), modTime: time.Unix(1444419364, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/TRANSLATORS", size: 1298, mode: os.FileMode(420), modTime: time.Unix(1447011494, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_bgBgIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xdc\x7d\x6d\x8f\x1c\x45\x9a\xe0\xf7\x96\xfa\x3f\x24\x1e\x59\x03\x52\xbb\x10\x70\x6f\x42\x34\x1c\x03\xbb\x30\x27\x60\xb8\x31\x23\x9d\x84\x50\x91\x5d\x95\xdd\x9d\xeb\xaa\xca\x22\x33\xcb\xc6\xb3\x5a\xc9\xd8\xc3\xab\x4e\xeb\x59\x2f\x73\x83\x98\x01\x63\xd8\xd9\xbd\xfd\x56\x6e\x77\xb9\xcb\xed\xee\xf2\x5f\xc8\xfa\x0b\xf7\x4b\x2e\x9e\x97\x88\x78\x22\x32\x32\xab\xda\x30\xf7\xe1\xb4\x5a\xc6\x5d\x19\xef\xf1\xc4\xf3\xfe\x12\x8f\xc7\xdd\x7e\x52\xf4\xb6\xab\x7f\xaa\x66\xd5\x51\xf5\x68\x79\xad\x5a\x54\x77\xab\x87\xea\xaf\x13\xf5\xff\xa7\xd1\xf2\x23\xf8\x61\xf9\xd1\xf2\x7a\x75\x80\x3f\xbc\x96\x96\xea\xc7\xe5\x17\xaa\xe5\x01\xfc\x77\x73\x63\x73\x63\x3f\x1b\x26\xdb\xd5\x37\xd5\x74\xf9\x69\x35\x55\x9d\x17\x9b\x1b\xfd\xb8\xd8\xdf\xc9\xe2\xbc\xbf\x5d\x7d\xaf\x7e\xbb\x4b\xbf\x26\x1f\x8e\x07\x59\xae\xda\x7e\xa7\x7e\x3b\xaa\xee\xe1\x44\x87\xea\xdf\x0f\xd4\x20\xc9\x60\xbc\x5d\xdd\x56\xd3\x9d\x54\x8b\xe5\xe7\x9b\x1b\x45\xba\x37\xea\xa6\xa3\xed\xea\x96\x6a\x36\x57\xcd\xa7\xd5\x69\x35\xe3\xdf\xb3\x49\xb9\x5d\x7d\xa5\x7e\xac\x7f\x9a\x8c\x61\xfc\x99\x1a\x7d\x0e\xeb\x56\x0b\x9d\xab\xff\x57\x73\xa8\x3d\xcc\x60\x43\xaa\x61\x9e\xec\xa5\x45\x99\xe4\xe1\x96\x38\xd6\x95\x64\xa7\x48\x4b\xb5\xd6\x1f\x54\x0b\x75\x02\x34\xc2\xe6\xc6\xe5\x24\x2f\xd2\x0c\x97\x35\x5b\x5e\x53\xbf\xcf\x97\x37\x37\x37\xc6\xf1\x9e\x6a\x7a\x07\x07\x81\x01\xe6\xcb\x4f\xaa\xe9\xe6\x46\x99\x0c\xc7\x83\x18\x46\xf9\xdf\xfa\x14\xaa\xd3\xcd\x8d\x41\x3c\xda\x9b\x60\x8f\x3f\xa8\xc5\xcf\xab\xe3\xcd\x8d\x5e\x9e\xa8\x76\xdd\x51\x72\x05\x86\xf9\x42\xfd\x0c\xe7\x72\x40\xab\xe9\x74\x3a\x9b\x1b\x93\x22\xc9\xbb\xe3\x3c\xdb\x4d\x07\x49\x37\x1e\xf5\xbb\x43\x3c\x4a\x75\x62\x38\xab\x5a\xa4\x9a\x55\x6d\xb1\x7a\xa8\x16\x75\x5c\xcd\x23\xba\xce\xe5\xef\xd4\x04\x0f\x23\xfe\xbb\x43\x87\x94\xf4\xd5\xc9\x76\xe3\x02\x76\xf1\x08\x76\x0f\xf3\x44\xaa\xd7\x54\x8d\xb0\x80\x3b\x85\xd9\x46\xf1\x30\x3c\x81\xba\xc9\x61\x9c\x0e\x60\xf9\x0f\x3b\x6a\x5c\xb8\x30\xd8\xee\x38\x2e\x8a\x2b\x19\x5c\xfa\x6d\x35\x12\xc0\xd2\x43\xf8\x39\x4f\xba\xe5\xd5\xb1\x1a\xea\x96\xda\xd8\x01\x5e\xf9\x0c\x2f\x03\x7a\x2a\x30\x52\x53\xaa\xc6\xa7\x30\x71\x2f\x1e\x97\xbd\xfd\x78\xfb\x15\xfa\x5f\x58\x49\x9e\x8c\x33\x75\x13\x59\x7e\x75\xbb\xfa\x8b\x3e\x5e\xb8\x77\x35\xa7\xba\xa7\x2c\xdf\x8b\x47\xe9\x6f\xe3\x12\x2f\xe5\x5b\xd5\xe0\x1e\x37\x51\x60\xa1\x6e\x01\xaf\x67\x98\xe6\x79\xa6\x6e\xfb\x5b\x01\x73\x08\x91\xea\xbc\xbb\x30\x01\x80\xaf\x5a\x4a\xb5\x88\x96\x1f\xd7\xe7\x80\x56\xc3\x74\x2f\xc7\x9b\xa4\x86\xd3\x48\xc1\xe9\xbc\xba\x07\x8d\xf5\x2c\xd0\x6c\x37\xcb\x2f\x89\xc1\xae\x21\xa0\x1f\xd3\xc5\xc3\x03\x6a\x99\x41\xed\x44\x8c\xbe\x68\xd8\x49\x3c\x52\x80\x43\x6d\x7f\xc0\x1b\x06\x28\x79\x88\x43\xcf\xe1\x48\x4f\x1b\x3a\x57\xf3\xcd\x8d\xb8\x3f\x54\xd7\x3e\x8e\x47\x89\xba\xbb\xdf\xab\x53\x80\x2d\x9c\x1a\xe8\x87\xcb\x9f\xf3\x3b\x57\x57\x83\xa0\x07\x97\x1d\xf7\x7a\xd9\x64\x54\x76\x8b\xa4\x2c\xd3\xd1\x5e\x41\x6f\x9d\xfa\x2c\xaa\x07\x04\x6c\x38\xaf\x80\x38\xb8\xf8\x96\x0e\x9b\x1b\x57\xb3\x89\x81\x67\x80\xc3\xe9\xf2\x33\xd8\xe2\xf2\xba\x33\x0c\xb7\xb3\x23\xe9\x86\xd7\x79\xb3\xfe\xb0\x78\x96\x45\x77\x37\x49\xfa\x0c\xbb\xea\xdb\x31\x34\x85\x83\xc5\xed\x2a\x40\x9d\x0c\x06\xea\xde\x3f\x98\x24\x45\xa9\xc6\xfc\xa3\x1a\xe6\xa6\xfa\x8a\x1b\x51\xe7\x05\x58\xe2\x21\x1e\x05\x23\x83\xb4\x28\x54\x53\x35\x9e\x83\x20\x71\xb6\x5e\x3c\xea\xc1\x71\x7e\xab\x26\x3a\x86\xdb\x86\x1f\xdf\x2d\x92\x38\xef\xed\xbf\x07\x47\x00\xff\x50\x58\x10\x90\x25\x20\x1f\xf3\x9e\x57\xc0\x35\x3c\xc0\x86\xc7\x87\xab\xf1\x16\xa3\x16\x92\xf5\xd5\x8f\x5f\xab\x9f\x0e\x71\x09\xe9\xa8\x28\xe3\xc1\x40\xad\x81\xff\x05\x08\xf3\x14\x51\x39\x00\xbf\x81\xa8\x32\x2d\x07\x8c\xbb\xbe\x50\xb7\x6e\xce\x00\xce\xca\xb4\x36\x88\x91\x2e\x07\x91\x09\xd2\x00\x84\x6b\x8d\xf8\x11\xba\xb1\x8b\xda\xeb\x75\x89\x4d\xfb\x59\xef\x92\x42\x5f\x80\xe4\x61\x5b\xbf\x57\xd3\x2c\xa2\xd7\xb2\xbd\x22\x42\x9c\xec\x77\x89\xaa\x83\xe8\x55\xec\x02\x48\x49\x4d\x81\x5b\x7f\x00\x63\x2d\xaf\x6d\xc1\xd3\x53\xa8\x65\x79\x53\x43\xca\xa7\x84\x4f\x10\x26\x5e\x88\xa3\x32\xce\xf7\x92\x72\xfb\x5c\x77\x47\x21\xda\x4b\xe7\xa2\xfd\x3c\xd9\xdd\x3e\x77\xbe\x38\xf7\x22\xae\x55\x43\xcf\xf2\x06\x5d\x18\x23\xec\x9b\x2f\x3c\x1d\xbf\xa8\x26\xc6\x6b\x38\xc1\xc7\x30\xa3\x3d\x6d\xd1\x3c\x80\x36\xd4\xc6\x0f\x35\xa0\xc3\xc3\x63\x38\x24\xc2\x79\x42\xaf\x10\xd1\x5a\x00\x36\x0d\xc8\x46\x78\xa8\x0a\xf1\x47\xfa\xe1\x69\x8a\xf1\x04\x80\xc5\x07\x13\x45\x77\xba\xfd\x1d\x22\xcd\x74\x48\xf0\x8c\xe7\x88\xd7\x01\x39\xbc\x79\xf5\xe2\x7f\x7f\x63\x2b\x7a\x3b\x2b\xca\xbd\x3c\xc1\x7f\xab\xff\xa8\x5e\xcf\x45\x04\x42\xd1\x3b\xe9\xab\xbf\x50\x30\xa6\x06\xe1\xeb\x6d\x7a\xb1\x77\xe9\x04\x60\x45\xb8\x35\xf5\x2b\xbe\x10\xe8\x89\x38\xfb\x7b\x35\xe2\x23\xb7\xb1\xd3\x70\x5f\x2d\x02\x49\x96\xe5\x08\x5a\x40\xb7\x81\x42\xa8\xd9\x88\xd8\x7c\x05\x87\xd8\x32\x9b\x6a\xa8\x61\xe8\xcf\x04\x03\x5b\x74\x38\x8f\x70\xa8\x23\x7c\xb2\x44\xe9\x7f\xf9\xd6\x5b\xbf\x7a\xf5\x17\x51\x32\xda\x4b\x47\x89\x3a\xe9\x68\x52\xee\xfe\x97\xee\x5e\x32\x4a\xf2\x78\xd0\xed\xa5\x04\x59\x87\x12\xb4\x71\xda\x23\xbc\xb9\x4f\xf4\x33\xc0\xb3\x06\xbLine truncated
|
||||
var _confLocaleLocale_bgBgIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xdc\xbd\x6b\x8f\x1c\x55\x9a\x20\xfc\x3d\x7e\x45\xe0\x96\xd5\x20\x95\x13\x01\xf3\x5e\x84\x28\x78\x69\x98\x86\x7e\x17\x68\x16\xd3\x9a\x95\x2c\x2b\x89\xca\x8c\xaa\x8a\x71\x56\x46\x76\x46\xa4\x0b\x33\x1a\xc9\xd8\xcd\x55\xab\x71\x8f\x87\xde\x46\x4c\x83\x31\x6c\xcf\xec\x7c\x4b\x97\x9d\xae\x74\x5d\xd2\x7f\x21\xf2\x2f\xec\x2f\xd9\xf3\x5c\xce\x39\xcf\x39\x71\x22\x32\xcb\x4d\xef\x87\x55\xab\xb1\x9d\x71\xee\xe7\x39\xcf\xfd\x92\x8c\x46\xdd\x7e\x5a\xf4\x36\xab\x7f\xae\x66\xd5\x61\xf5\x78\x79\xbd\x5a\x54\xf7\xaa\x63\xf5\xaf\x13\xf5\xff\xd3\x78\xf9\x31\xfc\xb0\xfc\x78\x79\xa3\x3a\xc0\x1f\xde\xc8\x4a\xf5\xe3\xf2\x4b\xd5\xf2\x00\xfe\x1b\x45\xbb\xf9\x5e\xba\x59\x7d\x5b\x4d\x97\x9f\x55\x53\xd5\x75\x11\xf5\x93\x62\x77\x2b\x4f\xc6\xfd\xcd\xea\x07\xf5\xd3\x3d\xfc\x31\xfd\x70\x34\xc8\xc7\xaa\xe5\xf7\xea\xa7\xc3\xea\x3e\x4e\xf2\x40\xfd\xfd\x51\xb4\x9b\x0e\x46\x9b\xd5\x1d\x35\xd3\x49\xb5\x58\x7e\x11\x15\xd9\xce\xb0\x9b\x0d\x37\xab\xdb\xaa\xd1\x5c\x35\x9e\x56\xa7\xd5\x8c\x7e\xce\x27\xe5\x66\xf5\xb5\xfa\xad\xf6\x65\x32\x82\xb1\x67\x6a\xe4\x39\xac\x57\x2d\x70\xae\xfe\xaf\xc6\x57\x6b\x9f\xc1\x46\x66\xd1\x38\xdd\xc9\x8a\x32\x1d\x87\x1b\xc2\x48\xfb\xe9\x56\x91\x95\x6a\x95\x3f\xaa\x06\x6a\xdf\xd4\x3f\xba\x9a\x8e\x8b\x2c\xc7\x15\xcd\x96\xd7\xd5\xcf\xf3\xe5\xad\x68\x94\xec\xa8\x86\x77\x71\x04\xe8\x3d\x5f\x7e\x5a\x4d\xa3\x32\xdd\x1b\x0d\x12\x18\xe2\x7f\xe8\xbd\x57\xa7\xd1\x20\x19\xee\x4c\xb0\xfd\x1f\xd4\xaa\xe7\xd5\x51\xd4\x1b\xa7\xaa\x55\x77\x98\xee\xc3\x18\x5f\xaa\x5f\xe1\x30\x0e\x68\x1d\x9d\x4e\x27\x9a\x14\xe9\xb8\x3b\x1a\xe7\xdb\xd9\x20\xed\x26\xc3\x7e\x77\x0f\x8f\x4f\x1d\x13\xce\xa8\x96\xa7\x66\x54\x5b\xab\x8e\xd5\x7a\x8e\xaa\x79\x4c\xd7\xb7\xfc\x9d\x1a\xfe\x38\xe6\x7f\x77\xf0\x6c\xd2\xbe\x3a\xce\x6e\x52\xc0\xfa\x1f\xc3\xae\x61\x96\x58\x75\x9a\xaa\x01\x16\x11\x4e\x35\x4c\xf6\xc2\xa3\x47\xe9\x5e\x92\x0d\x60\xe1\xc7\x1d\x35\x26\xdc\x90\xda\xe6\x28\x29\x8a\xfd\x1c\xae\xf8\x8e\x1a\x05\xc0\xe6\x58\xfd\x3a\x4e\xbb\xe5\xb5\x91\x1a\xe7\xb6\xda\xd1\x01\x5e\xf0\x0c\x8f\x1f\xfa\x29\x80\x51\xb3\xa9\xb6\xa7\x6a\xce\x5e\x32\x2a\x7b\xbb\xc9\xe6\x6b\xf4\x67\xa4\xba\x8e\x72\x75\xf4\xf9\xf8\xda\x66\xf5\x67\x7d\xa2\x70\xcb\x6a\xba\x59\x94\x8f\x77\x92\x61\xf6\x51\x52\xe2\x2d\x7c\xa7\xbe\xdf\xe7\x16\x0a\x06\xd4\xb9\xc3\x7d\xec\x65\xe3\x71\xae\xee\xf6\x3b\x01\x5c\x00\x79\xea\x88\xbb\x30\x3a\x00\xa9\x5a\x44\xb5\x88\x97\x9f\xd4\x26\x80\x46\x7b\xd9\xce\x18\x6f\x8e\xda\x4d\x63\x05\x8f\xf3\xea\x3e\xb4\xe5\x29\xa0\xd5\x76\x3e\xbe\x22\x86\xba\x8e\xe0\x7c\x44\xf7\x0c\x4f\xa4\x79\x7c\xb5\x09\x31\xf6\x22\xbc\x89\x64\xa8\xc0\x84\x9a\xfe\x88\x37\x0a\x40\x71\x8c\x03\xcf\xe1\x20\x4f\x1b\xfa\x56\xf3\x28\xe9\xef\xa9\x6b\x1e\x25\xc3\x54\x5d\xd7\xef\xd5\xfe\x61\xf9\xa7\x06\xca\xe1\xb2\xe7\xfc\x8c\xd5\x7d\x20\xa0\xa9\xeb\x4d\x7a\xbd\x7c\x32\x2c\xbb\x45\x5a\x96\xd9\x70\xa7\xa0\xb7\x4c\x5d\x16\xd5\x23\x02\x2d\x9c\x55\xc0\x97\xba\xeb\x96\xf6\xd1\xb5\x7c\x62\x60\x17\x80\x6e\xba\xfc\x1c\x76\xb7\xbc\xe1\x0c\x42\xcd\xec\x38\xba\xdd\x0d\xde\xa7\x37\x28\x9c\x61\xd1\xdd\x4e\xd3\x3e\xc3\xa9\xfa\x72\x04\x0d\xe1\x40\x71\xa3\xd1\x68\x32\x18\xa8\xbb\xfe\xed\x24\x2d\x4a\x35\xe0\x1f\xd5\x18\xb7\xd4\x47\xdc\x82\x3a\x27\xc0\x02\xc7\x78\x06\xf4\xda\xb3\xa2\x50\x2d\xd5\x68\x0e\xde\x53\x33\xf5\x92\x61\x0f\x0e\xf1\x3b\x35\xc9\x11\x5c\x70\x14\x5d\x2a\xd2\x64\xdc\xdb\xbd\x1c\xd1\x9f\x0a\xb5\x01\xfa\x03\xb4\xa2\x1f\x6c\x3b\x04\xc3\x1b\x0b\xbf\x2f\x5c\x85\xb7\x88\xa8\x97\xf7\xd5\x6f\xdf\xa8\x5f\x1e\xa8\xb9\xb3\x61\x51\x26\x83\xc1\xe5\x88\xff\x02\x28\xf0\x14\xb1\x32\x80\xb8\x86\x9d\x32\x2b\x07\x8c\x91\xbe\x54\x17\x6c\x76\x0d\x87\x63\x1a\x1b\x5c\x47\x57\x81\x68\x02\xb1\x39\xc2\xaf\x46\xe2\x08\xc5\xd8\x45\xed\xf1\x86\x40\x90\xfd\xbc\x77\x45\xe1\x25\x40\xd9\xb0\x9f\xdf\xab\x59\x16\xf1\x1b\xf9\x4e\x11\x23\x92\xf5\x7b\xc4\xd5\x41\xfc\x3a\x76\x01\x74\xa3\x66\xc0\x3d\x3f\x82\xa1\x96\xd7\x37\xe0\x81\x29\xc4\xb1\xbc\xa5\xc1\xe2\x33\x42\x17\x08\x01\x2f\x25\x71\x99\x8c\x77\xd2\x72\xf3\x5c\x77\x4b\xe1\xcf\x2b\xe7\xe2\xdd\x71\xba\xbd\x79\xee\x7c\x71\xee\x65\x5c\xaa\x86\x95\xe5\x4d\xba\x24\xc6\xc2\xb7\x5e\x7a\x36\x79\x59\x4d\x8c\xc7\x7f\x82\x60\x3f\xa3\x2d\x6d\xd0\x3c\x80\x1a\xd4\xbe\x1f\x68\x98\x86\x17\xc6\x50\x47\x14\xf0\x84\x9e\x1b\x62\xad\x00\x24\x1a\x00\x8d\xf1\x4c\x15\x3e\x8f\xf5\x13\xd3\x64\xe0\xa9\x08\xa0\x50\x51\x92\x6e\x7f\x8b\x48\x2c\x9d\x11\x3c\xd7\x39\xe2\x6b\xc0\x01\x6f\x5f\xbb\xf8\x9f\xdf\xda\x88\xdf\xcd\x8b\x72\x67\x9c\xe2\xdf\xd5\x7f\x54\xaf\x17\x62\x82\x9c\xf8\xfd\xec\xf5\x5f\x74\x22\x35\x06\xdf\x6d\xd3\xd3\xbc\x47\xfb\x87\xf5\xe0\xc6\xd4\xaf\xf0\x1e\xa0\x23\xe2\xe3\x1f\xd4\x78\x8f\xdd\xb6\xb2\xdd\xae\x5a\x01\x92\x21\x43\xd6\x9b\xc1\x35\x8c\xf9\xd5\x4c\x44\x41\xbe\x86\xd3\x6b\x9e\x49\xb5\xd3\xb0\xf3\x27\xba\xfb\x0d\x3a\x95\xc7\x38\xd0\x21\xbe\x4d\x22\xd9\xbf\x7a\xe7\x9d\x5f\xbf\xfe\x8b\x38\x1d\xee\x64\xc3\x54\x9d\x70\x3c\x29\xb7\xff\xdf\xee\x4e\x3a\x4c\xc7\xc9\xa0\xdb\xcb\x08\xa2\x1e\x48\x88\xc6\x59\x0f\xf1\xc6\x3e\xd5\xd0\x8f\x87\xac\xe8\x60\x31\x50\x14\xb4\x9f\x12\xed\x7f\x08\xa0\x11\x5f\xbc\xf8\x96\xda\x4e\xb9\x0b\x8Line truncated
|
||||
|
||||
func confLocaleLocale_bgBgIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4339,12 +4339,12 @@ func confLocaleLocale_bgBgIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_bg-BG.ini", size: 68713, mode: os.FileMode(493), modTime: time.Unix(1446027988, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_bg-BG.ini", size: 68826, mode: os.FileMode(493), modTime: time.Unix(1448150136, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_deDeIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\xbd\xcb\x8e\xe4\xc6\x92\x26\xbc\x2f\xa0\xde\x81\xaa\x46\xfd\x92\x80\x8c\x10\x74\xf4\xcf\x05\x82\x42\x9a\xac\x7b\x1d\xd5\xad\x95\x25\x09\x90\x20\x84\x18\x41\x8f\x08\x76\x32\xc8\x10\x9d\xcc\x54\x66\xa3\x81\x59\xf4\x72\xd6\x83\x59\x0c\xd0\x1b\x61\x1e\x41\x2b\xed\xf2\x4d\xfa\x49\xc6\x3e\x33\xf3\x1b\xc9\xc8\xaa\x73\x34\xbd\x90\x2a\x83\x7e\x37\x37\x37\xb7\xbb\xe7\x87\xc3\xb2\x30\x76\xbd\x78\x5c\xd6\x99\x29\xeb\x4d\xbe\xde\x99\xf6\x24\xb3\xa6\x5a\xd9\x2e\xdb\x9a\x5d\x63\x3b\xd3\x99\x36\x7b\x5a\x76\xb3\x33\xd3\x5e\x94\x6b\x73\x42\xdf\xed\x7a\xd7\x96\x66\x65\xea\x8c\x1a\x3e\x6d\xee\xde\xb9\x7b\x67\xd7\xec\xcd\xe2\x19\xfd\xef\xee\x9d\x22\xb7\xbb\x55\x93\xb7\xc5\xe2\xe6\x7f\xaf\x4c\x6b\xcb\xf5\xae\xbb\x7b\xc7\xfc\x7a\xa8\x9a\xd6\x2c\x1e\xb7\xe7\x7d\x5d\x98\x9a\x9a\x98\xea\xb0\x78\x56\x56\x1b\x6a\x63\xcb\x6d\xbd\x2c\xeb\xc5\x69\xbd\x37\x15\x97\xf2\x97\xa6\xef\x16\xa7\xab\xe4\x53\x7f\x58\x7c\x63\xb6\xa5\xed\x68\x06\x2d\xbe\xb6\xfc\xcb\xb4\x83\xcf\x97\x66\x65\xcb\xce\x2c\xbe\xa7\x7f\x0d\xfd\x71\xf7\xce\x05\xe6\xd2\xd4\x8b\xef\xe4\xdf\xbb\x77\x0e\xf9\xd6\x2c\xce\xa4\xb0\x33\xfb\x43\x95\x53\xfd\xef\x9a\xb6\xa2\xef\x77\xef\x54\x79\xbd\xed\xb9\xc6\xa1\x05\x60\xee\xde\x59\xb7\x86\x6a\x2c\x6b\x73\x49\xab\xa0\x21\xab\xca\xd4\xf3\xf9\xfc\xee\x9d\xde\x9a\x76\x79\x68\x9b\x4d\x59\x99\x65\x5e\x17\xcb\x3d\x56\xfa\xc0\xd4\x7d\x77\x6d\x5a\x29\xc8\x68\xd5\xd9\xde\xec\x5a\x59\x87\x29\x68\xb9\xcb\xdc\x02\xf6\x5b\x53\x35\xdb\x6d\x97\xe5\x95\x05\x28\xd1\x5b\x9d\xef\x43\x07\xf8\x41\x00\xdc\xe7\x65\xb5\x78\x3c\x7b\x49\xff\x60\xee\xd6\x5e\x36\x04\xe3\x37\xf2\x47\x07\x40\x2c\xbb\xab\x83\xf1\x5f\xb2\x95\xb1\xdd\xcd\x6f\x5d\xb9\x05\x3c\xd6\xf9\xa1\x5b\xef\xf2\xc5\x43\xf9\x17\x03\xb5\xe6\xd0\x10\x8c\x9a\xf6\x8a\x60\xe7\xfe\xbc\x7b\xa7\x69\xb7\x79\x5d\x5e\xe7\x1d\x80\xf5\x9a\x7f\x58\xfe\x71\xf7\xce\xbe\x6c\xdb\xa6\x25\x88\x94\x86\x26\x7d\xf7\x0e\x81\x62\x89\x5e\x16\xaf\x4c\x6f\x6c\x16\xf7\x82\xa2\x7d\xb9\x6d\x01\x53\x94\x66\x2f\xf9\x07\x77\x83\xb2\x4d\xd3\x9e\x6b\xb3\x7c\x45\x28\x75\xc8\x2b\xe0\xda\xb8\x13\x9a\x8e\x74\x30\x98\x4a\x5e\xd3\xe6\x70\x69\x5c\x40\x38\x49\xfb\x7c\x89\xce\xa8\x52\x5e\xec\x09\xca\x87\xbc\x36\xd5\xe2\x14\x7f\xcf\xde\xe0\x6f\x2a\x58\xaf\x9b\xbe\xee\x96\xd6\x74\x1d\x6d\x80\x5d\x7c\xdd\xd4\x5d\x43\x67\x80\xb7\xb5\xaf\x19\x64\xbe\xf0\x71\xfa\xfd\xaa\xe9\xfd\x76\x2f\x1e\x51\xa3\xec\x0d\xff\xd0\x12\xdf\x0c\x45\x26\x1b\x34\xe6\x45\xd9\xe5\xc6\x98\x02\xcb\x2a\xb7\xe7\x40\x40\x60\x63\x5f\x55\x04\xcc\x5f\x08\x22\x9d\x5d\xbc\xa1\x5f\x04\x0a\xf9\x75\xf7\x4e\x69\x2d\xfd\xb5\x78\xce\xff\xa0\x93\x75\x5e\xaf\xb1\xa8\xd5\xaa\x35\x84\x9c\xdc\xf1\x8f\xd6\xe4\xed\x7a\xf7\x13\x66\x8e\x3f\x16\x67\x3d\x8a\x18\x45\x8f\xec\x35\x70\xcd\xe3\x99\x0e\xb3\xa0\xd5\xac\x2a\xb3\xa7\x41\x9a\xc2\x2c\x1e\xd2\xff\xb8\x77\xac\x23\xaf\x2a\xea\x5e\xff\x5a\x3c\x97\x7f\x75\x47\xba\xb2\x23\x78\xc4\xdf\xb2\xcd\xcd\x1f\x6d\x46\xc7\xad\xdb\xe7\x15\xd0\x30\x3b\xeb\x72\xa0\x6a\xd1\xac\xcf\xe9\xc8\x80\x02\xd0\xf8\xdf\x9b\x1a\x64\x64\x6b\x89\x9c\xd4\xa6\xdd\xe5\xd5\x2a\x7b\xc4\x35\xb2\xea\xe6\xb7\x7e\xd3\x9d\x64\x55\x49\x98\x51\x94\x6d\xb6\x2a\xbb\xce\xd0\x5f\x26\xfb\x22\xcf\xa8\xb3\xad\xe9\x16\xf7\x96\x2b\x3a\xac\xe7\xf7\xb2\x5d\x6b\x36\x8b\x7b\xf7\xed\xbd\x2f\x9f\xf6\x65\x61\x2a\x02\xbf\xfd\xe2\x93\xfc\x4b\x22\x59\x75\xde\x67\x45\x4f\x40\x39\xa1\x63\x71\xd1\xb4\xf4\x23\x2b\xa9\x75\x5d\x5c\xe6\x84\x81\xfd\x06\x7d\x12\x30\x32\xa6\x07\xd9\xcd\x6f\x44\xa3\x68\xde\x1f\x00\x72\xbf\xf4\xf4\x69\x59\xac\x84\x58\xf2\x44\x89\xf6\xdd\xfc\x4e\x27\xab\xcb\x5e\x5e\x9d\xfd\xe3\x8b\x93\xec\x0d\x91\xca\x6d\x6b\xf8\x6f\xfa\x1f\x35\xf8\x2c\x23\xc0\xb5\xd9\xdb\xf2\xd1\x03\x82\x3f\xb5\x16\xf8\x3c\xa2\x03\x51\xaf\x68\xba\x03\x64\x43\x05\x9c\x5e\x5f\x4e\xbf\x40\x56\x6d\x47\x64\xd5\x76\xa3\xad\x9a\x20\x00\xd4\x05\xd3\x0d\xdf\x85\x10\x0e\xfa\xac\x80\x7e\xc0\xc0\xc3\xf9\x30\x20\xc1\x99\x2d\x05\x94\xcf\xeb\xba\x79\xf4\x60\xf6\xb8\xde\x02\x61\xf7\x65\x97\xf5\xdd\xe6\xbf\x2e\x69\x5e\xa6\xcd\xab\xe5\xba\xcc\x7e\x30\x25\x50\x89\xce\xd8\xb5\x6c\x2a\x2f\x9b\xd6\x65\x6d\x45\xd4\x8e\xd0\xe4\xec\xec\xc5\xec\x65\x53\xf4\x16\x53\xeb\x76\x8b\x37\x9b\xbc\xa0\xe2\x5f\x2a\xc0\x4e\xc7\x7f\x44\xf0\xc0\xe4\xca\x03\x15\x66\xd7\x7d\x3b\x06\x55\xe6\x27\x4f\x9d\x9b\xb6\x5d\x12\x5d\xee\xae\x00\x7c\xee\xf5\x96\xfa\xdc\x67\x91\xb7\x9b\xac\xc6\x8d\x93\x55\x86\xaa\x10\xe9\xaf\xb5\xa3\xb2\xbe\x20\x24\x2c\x68\x1b\x3c\x9c\x46\x7d\xe0\xb3\xf4\x81\xed\xc9\xee\xcd\xef\x31\xd9\x96\x1f\xb3\x7b\x99\xa9\xbb\x1d\x93\x16\xea\xb3\x6e\x96\x42\x5e\x40\xf0\x0b\x22\x3f\x74\x66\x96\x72\x19\x09\x9d\x5b\x3c\xea\xb3\xf3\xbc\xa6\x5d\x66\x20\x87\xeb\x89\x76\x5c\xe7\x58\x98\xfc\xbc\x2b\x2f\xf8\xca\x3a\xc9\x9a\x1d\x41\x1f\x43\x31\xa9\x92\x7e\x88\x4a\x82\x32\x11\xb0\xf8\x1c\xc9\x9d\x13\x43\xc6\x91\x38\xc5\x86\x47\x84\xcd\x49\xf3\x99\xbf\x0e\x8e\xc0\xe6\xee\x1d\xb7\xcf\x82\xa0\xa7\x55\xb5\x35\xfb\x29\xd2\x05\x7e\x81\x01\x77\x5a\x03\x81\xe8\xab\x15\x0c\x73\x05\x6e\x9f\x9f\xd1\x82\x32\x20\x58\x4c\x99\xb9\xae\xf0\x19\x37\x7f\x6c\x79\x15\x20\x4a\xb2\xaf\x81\x26\x65\xdf\x3Line truncated
|
||||
var _confLocaleLocale_deDeIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\xbd\x5b\x6f\xdc\xc6\xb6\x20\xfc\xce\x5f\x41\x7b\xc3\x48\x02\xa8\xdb\x70\x72\xbe\x6f\x06\x41\x98\x8c\x64\xf9\x16\x4b\xb6\x4e\xa4\x24\x40\x0c\xa3\xcd\x6e\x56\x77\x73\x37\x9b\xec\xb0\x48\xc9\xd2\xc1\x01\xf6\xc3\x79\x9c\xe7\xc1\xc1\x60\x80\xfd\x62\xcc\x4f\xc8\x53\xde\xf4\x4f\xf6\x2f\x99\x75\xab\x2b\xd9\xb2\x93\x8d\xb3\x81\x1d\xab\x8b\x75\x5d\xb5\x6a\xd5\xba\x57\xbe\xdb\xcd\x0a\xa5\x17\xd9\x93\xb2\x4e\x55\x59\x2f\xf3\xc5\x5a\xb5\x07\xa9\x56\xd5\x5c\x77\xe9\x4a\xad\x1b\xdd\xa9\x4e\xb5\xe9\xb3\xb2\x9b\x9c\xab\xf6\xb2\x5c\xa8\x03\x28\xd7\x8b\x75\x5b\xaa\xb9\xaa\x53\x68\xf8\xac\x49\x92\x75\xb3\x55\xd9\x73\xf8\x4f\x52\xe4\x7a\x3d\x6f\xf2\xb6\xc8\x6e\xff\xcf\x5c\xb5\xba\x5c\xac\xbb\x44\xbd\xdf\x55\x4d\xab\xb2\x27\xed\xa6\xaf\x0b\x55\x27\x6b\x55\xed\xb2\xe7\x65\xb5\x54\x89\x2e\x57\xf5\xac\xac\xb3\xc3\x7a\xab\x2a\xfc\x46\x05\x4d\xdf\x65\x87\x73\xbf\xa4\xdf\x65\x3f\xa8\x55\xa9\x3b\x18\xb9\x85\xc2\x96\x7e\xa8\x36\x2c\xbd\x52\x73\x5d\x76\x2a\xfb\x19\xfe\x55\xf0\x47\x72\x89\x73\x68\xea\xec\x27\xfe\x37\xd9\xe5\x2b\x95\x9d\xd3\xa7\x4e\x6d\x77\x55\x0e\x95\x7f\x6a\xda\x0a\x8a\x93\x2a\xaf\x57\x3d\x7d\xdf\xb5\x08\x8a\x64\xd1\x2a\xf8\x3e\xab\xd5\x15\x4c\x1d\x06\xab\x2a\x55\x4f\xa7\xd3\xa4\xd7\xaa\x9d\xed\xda\x66\x59\x56\x6a\x96\xd7\xc5\x6c\x8b\xab\x3b\x52\x75\xdf\xdd\xa8\x96\x3f\xa4\xb0\xd2\x74\xab\xd6\x2d\x4d\x5f\x15\xb0\xc6\x59\xae\x11\xd4\x2b\x55\x35\xab\x55\x97\xe6\x95\x4e\xa8\xab\x3a\xdf\xba\xd6\xf8\x23\x51\xdb\xbc\xac\xb2\x27\x93\x53\xf8\x07\xa6\xac\xf5\x55\x03\x10\x3d\xe3\x3f\x3a\x58\xfb\xac\xbb\xde\x29\x5b\x90\xce\x95\xee\x6e\x3f\x74\xe5\x0a\x40\xb0\xc8\x77\xdd\x62\x9d\x67\x8f\xf9\xdf\x04\x6a\xef\x1a\x00\x4a\xd3\x5e\x03\xac\xcc\x9f\x49\xd3\xae\xf2\xba\xbc\xc9\x3b\x84\xce\x6b\xfa\xa1\xe9\x47\xb2\x2d\xdb\xb6\x69\x01\x08\xa5\x82\xa9\x26\xb0\xfa\x19\x76\x91\xbd\x52\xbd\xd2\xa9\xd7\x05\x7e\xd9\x96\xab\x16\x61\x88\x1f\xd3\x53\xfa\x81\x7d\xe0\xa7\x65\xd3\x6e\xa4\x51\x3e\x07\xa4\xd9\xe5\x15\x62\xd3\xa0\x0b\x98\x09\x37\x0f\x67\x91\xd7\xb0\x15\xf4\xd1\x2f\x07\x9c\x83\x2d\xbd\xc2\xae\xea\x24\x2f\xb6\x00\xd5\x5d\x5e\xab\x2a\x3b\xc4\xbf\x27\x67\xf8\x77\x92\x2f\x16\x4d\x5f\x77\x33\xad\xba\x0e\xe0\xad\xb3\x97\x4d\xdd\x35\x80\xe1\xb4\x87\x7d\x8d\x60\xb2\xdf\x9e\x04\xc5\xd7\x4d\x6f\xb7\x36\x3b\x86\x26\xe9\x19\xfd\xe0\x0f\xb6\x11\x7e\x51\x69\xd8\x14\x17\xa3\x67\x4b\xa5\x0a\x5c\x4e\xb9\xda\x20\x96\x01\xc6\xf5\x55\x05\x00\xfc\x15\xe0\xd0\xe9\xec\x0c\x7e\x01\x00\xf8\x57\x52\x6a\x0d\x7f\x64\x2f\xe8\x9f\x04\xb6\xae\x5e\xe0\x52\xe6\xf3\x56\x01\xfe\x41\x97\x6f\xb4\xca\xdb\xc5\xfa\x6d\xc2\xff\x66\xe7\x3d\x96\x23\x0e\x8e\xef\x2a\xa2\x93\x45\x25\xee\x3e\x83\x05\xcc\x2b\xb5\x4d\x16\x4d\xa1\xb2\xc7\xf0\x1f\xe8\x16\x27\x9e\x57\xd5\xdb\x44\xfe\xc8\x5e\xf0\xbf\x0c\xfb\xae\xec\x60\xf5\x7e\x51\xba\xbc\xfd\xbd\x4d\xe1\x0c\x75\xdb\xbc\x42\x34\x4b\xcf\xbb\x1c\x30\xb1\x68\x16\x1b\x38\x0b\x78\x9e\x61\xe0\x9f\x55\x8d\xf4\x60\xa5\x81\x2e\xd4\xaa\x5d\xe7\xd5\x3c\x3d\xa6\x1a\x69\x75\xfb\xa1\x5f\x76\x07\x69\x55\x02\x02\x14\x65\x9b\xce\xcb\xae\x53\xf0\x97\x4a\xbf\xc9\x53\xe8\x6b\xa5\xba\xec\xfe\x6c\x0e\x67\x70\x73\x3f\x5d\xb7\x6a\x99\xdd\x7f\xa0\xef\x7f\xfb\xac\x2f\x0b\x55\x01\xb0\xf5\x37\x0f\xf3\x6f\x81\xf6\xd4\x79\x9f\x16\x3d\xc0\xe2\x00\xb0\xfe\xb2\x69\xe1\x47\x5a\x42\xeb\xba\xb8\xca\x01\xd1\xfa\x25\xf6\x09\x50\x48\xe9\x90\xa7\xb7\x1f\x80\xe0\xc0\xb4\xef\x25\xb8\x05\x50\x32\x2b\xe6\x4c\xf4\x68\x9e\x40\xc3\x6e\x7f\x83\x73\xd3\xa5\xa7\xd7\xe7\xff\x7a\x72\x90\x9e\x01\xc9\x5b\xb5\x8a\xfe\x86\xff\x40\x83\xaf\x52\x00\x59\x9b\x5e\x94\xc7\x47\xd3\x04\x1a\x33\x70\x8e\x01\xe9\xeb\x39\x4c\x36\xc4\x2a\xfc\x8e\x27\xd3\x7e\x86\x5f\x09\x92\x51\xa0\x8e\xba\x8b\xb6\x67\x78\xb0\xa1\x39\x11\x03\xdb\x9c\xa8\x01\x94\x0a\x80\x8f\x08\x68\x78\x00\x14\xd2\xd1\x54\x97\x0c\xc2\x17\x75\xdd\x1c\x1f\x4d\x9e\xd4\x2b\x44\xcb\x6d\xd9\xa5\x7d\xb7\xfc\xef\x33\x98\x91\x6a\xf3\x6a\xb6\x28\xd3\x5f\x54\x89\x98\x03\x87\xe8\x86\xf7\x92\xd6\x3b\x4d\xb4\xae\x80\x7a\x01\x62\x9c\x9f\x9f\x4c\x4e\x9b\xa2\xd7\x30\xad\x6e\x9d\x9d\x2d\xf3\x22\xd1\xbf\x56\x08\x31\x19\xfc\x18\xa0\x80\x13\x2b\x77\xf0\x2d\xbd\xe9\xdb\x21\x80\x52\x3b\xf1\x69\xa2\xda\x76\x06\x14\xb6\xbb\x46\x88\x53\x9f\x77\x54\xa7\x2e\x8b\xbc\x5d\xa6\x35\xde\x18\x69\xa5\xa0\x0a\x50\xf0\x9a\xfb\x29\xeb\x4b\x40\xbb\x02\x60\x6f\x21\x34\xe8\x02\x8b\xb9\x0b\xdc\x93\xf4\xfe\xf4\x3e\x91\x60\xfe\x31\xb9\x9f\xaa\xba\x5b\x13\xd9\x98\x26\x75\x33\x63\xd2\x81\xb4\xbb\x00\xca\x02\xe7\x63\xc6\x17\x0a\xd3\xaf\xec\xb8\x4f\x37\x79\x0d\x3b\x4b\xe0\x75\x57\x0c\xec\xb2\xcc\xb0\x50\xf9\xa6\x2b\x2f\xe9\xda\x39\x48\x9b\x35\xc0\x1d\x47\x22\x32\xc4\xfd\x00\xf9\x43\xb2\x03\x90\xa2\x83\xc3\xb7\x87\x07\x16\x43\xbd\x04\x09\x8e\x01\x7d\x83\xd6\x13\x4b\xde\xc7\x01\x93\x98\xed\x65\x8c\x3c\xac\xaa\x95\xda\x8e\xd0\x25\xbc\xe6\x09\x66\x87\x35\xa2\x0d\x14\x6a\x42\x2b\x53\x6e\xf6\xf7\x39\xac\x25\x45\xac\xf2\xe9\x2d\x55\x65\xee\xe0\xf6\xf7\x15\x2e\x00\x09\x0f\xef\xa7\xa3\x3b\xe9\x0f\x4d\xd3\x4d\xe0\x8e\xbd\x41\x34\x83\xb6\xb8\x9f\xae\xa6\x19\x01\xa6\xa8\x88\x97\x70\x2d\x75\x7a\xa5\xda\x82\x39\x09\x3a\xb8\xdLine truncated
|
||||
|
||||
func confLocaleLocale_deDeIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4359,12 +4359,12 @@ func confLocaleLocale_deDeIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_de-DE.ini", size: 48023, mode: os.FileMode(493), modTime: time.Unix(1446117606, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_de-DE.ini", size: 48077, mode: os.FileMode(493), modTime: time.Unix(1448150138, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_enUsIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xeb\x72\xdc\x48\xae\xe6\x7f\x3e\x05\xdb\x13\x5e\xdb\x11\x72\x39\xba\x7b\x6f\xd1\xd1\x76\xaf\x2c\xb5\x2f\x73\x2c\x4b\x63\xa9\x67\x76\xd6\xe1\x60\xb3\x8a\xac\x2a\x8e\xab\xc8\x6a\x92\x65\x59\x73\xe2\x44\xec\x03\xec\x03\xec\xbe\xde\x79\x92\x05\x3e\x00\x79\x21\x59\x92\xdd\x33\x71\xfe\x48\xac\x4c\xe4\x0d\x89\x44\x22\x91\x00\x32\xdf\xed\xb2\xa2\xec\x16\xe9\xd3\xf4\x38\xdd\xe5\x55\xbd\x29\xbb\x2e\xed\xca\xcd\xf2\xf1\xba\xe9\xfa\xb2\x48\x5f\x56\x3d\xfd\x6e\x3f\x55\x8b\x32\x49\xd6\xcd\xb6\x24\xd0\x57\xf4\x2f\x29\xf2\x6e\x3d\x6f\xf2\xb6\xa0\x84\x53\xfb\x4e\xca\xcf\xbb\x4d\xd3\x32\xd0\xcf\xf2\x95\xac\xcb\xcd\x8e\xcb\xd0\xbf\xa4\xab\x56\x75\x56\xd5\xf4\xf3\x92\xbe\xd2\xd7\xb5\xa4\x34\xfb\xde\x92\xce\xf7\xbd\xa4\xed\x77\x96\xf4\xcb\x2e\x69\xcb\x55\x45\xbd\x69\x29\xe9\x9d\x7e\x26\xd7\xe5\xbc\xab\x7a\x6e\xe9\x2f\xf2\x95\x7c\x2a\xdb\xae\x6a\xb8\xf6\x3f\xcb\x57\xb2\xcb\x57\x0c\x70\x41\xff\x92\xbe\xdc\xee\x36\x39\x0a\x5c\xe9\x67\xb2\xc9\xeb\xd5\x5e\x60\xde\xe8\x67\xb2\x68\x4b\xca\xca\xea\xf2\x9a\x52\x4f\xf0\x63\x36\x9b\x25\x7b\x42\x42\xb6\x6b\x9b\x65\xb5\x29\xb3\xbc\x2e\xb2\xad\x0c\xf3\x17\x4a\x4f\x35\x3d\xa5\xf4\x94\xd3\x31\x84\xb2\xa0\xa1\x66\x79\xa7\xe3\x20\x5c\xd2\xc8\xf3\x2e\x41\x55\x75\xbe\xb5\xd2\xfc\x99\x94\xdb\xbc\xda\x30\xd6\x1e\xf3\x07\x75\xbc\xeb\xae\x1b\xe0\xf6\x42\x3f\x09\x09\x59\x7f\xb3\x2b\x81\x83\xc7\x57\xf4\x95\x2c\xf2\x5d\xbf\x58\xe7\xdc\x4f\xf9\x4a\x08\x68\xd7\x10\x32\x9a\xf6\x06\x70\xf6\x23\x69\xda\x55\x5e\x57\x7f\xcf\x7b\x41\xd0\x79\xf0\x33\xd9\x56\x6d\xdb\x30\x6e\xcf\xf0\x91\xd0\xd0\x33\xae\x87\x52\xde\x12\x16\x82\x5a\x38\x67\x5b\xad\x5a\x41\x23\x67\x9e\xe1\x17\xd7\xc2\x79\xcb\xa6\xfd\xa8\x19\x2f\xf8\x73\x50\x94\x3a\xa1\xb9\x71\xfb\x79\x4d\x88\xd7\xdc\x33\xfc\x88\x00\xba\x24\x2f\xb6\x84\xca\x5d\x5e\x97\x8c\xa3\x63\xfe\x45\x78\xa1\x5f\x49\xbe\x58\x34\xfb\xba\xcf\xba\xb2\xef\xab\x7a\xc5\xc8\x3e\x96\xa4\xf4\x52\x93\x92\x20\xcf\xa5\xdd\x34\x7b\x37\x9d\x94\xfe\x57\xfa\x99\x5e\xc8\x4f\xc9\x0b\x0a\x21\xd3\x95\xe4\x91\x74\xd9\xb2\x2c\x0b\x19\x4b\x97\xbe\xa0\xef\x64\xb7\xdf\x6c\x08\x6b\xbf\xed\xcb\xae\xe7\x42\x17\xf4\x9b\xc6\x2f\xbf\x93\xaa\xeb\xe8\x83\x92\x5f\xe3\x23\xa1\xa9\xab\x17\x18\xcc\x09\x3e\x92\xe4\x7d\x57\xe6\xed\x62\xfd\x21\x91\xff\xe8\x2b\x7f\x30\xed\x1d\x9a\x54\x26\x24\x25\x22\x69\xc1\x1a\x48\x16\x4d\xc1\x3f\x4e\xe8\x1f\x55\x5d\xd5\x5d\x9f\x6f\x36\x1f\x12\xfd\x60\x30\xf9\x92\x09\xe8\xab\x1e\x58\xd0\xc4\xf4\xb2\x2f\x77\x1d\xcf\x60\xfa\xa2\x6a\xbb\xfe\x71\x5f\x11\xb1\xbe\xdb\xd7\x49\xd1\x2c\x3e\xd2\x32\xe0\x25\x8d\x96\x5f\x2f\x53\x42\xd6\x03\x5a\x08\xed\xbe\xae\x09\x3d\xe9\xcb\x86\x50\x46\xcd\x54\xd4\xfe\x29\xa0\x8f\xd2\xdd\xa6\xcc\x3b\x02\x29\xf3\x22\xfd\x31\x4f\xfb\xbc\x5d\x95\xfd\xd3\x7b\xd9\x9c\x96\xdf\xc7\x7b\xe9\xba\x2d\x97\x4f\xef\xdd\xef\xee\x3d\x7b\xb9\xa7\x62\x9b\xaa\x2e\xbb\x1f\x9f\xe4\xcf\xd2\x45\x4e\x39\x84\xc6\x9b\x74\x5e\x2e\x79\xb5\x51\x5b\x29\x51\x79\xbd\xe2\x95\x76\xd3\xaf\xb9\x41\xa2\x04\xfa\xe8\x52\x5e\xea\xdf\x24\x3c\x01\xc4\x0a\xb2\x62\x6e\x6c\x0d\x1d\x42\x72\x4b\x13\x70\x76\x73\xf9\xa7\x37\x47\xe9\x05\xf1\xb6\x55\x5b\xe2\x9b\xfe\x50\x89\xef\x53\x1a\xed\x55\x75\xfa\x7c\x96\x50\x59\x43\xc8\x69\xde\xe7\x73\xee\xbb\x9b\x7d\xce\x94\x45\xe8\xf2\xb0\x14\x99\x5b\x82\x33\x76\x7d\x34\x2d\x53\x0b\x99\xea\xd0\xe5\xef\xea\x78\xcb\x3c\x80\xd2\x1d\x66\x2f\x04\x67\x54\x55\xfa\xfa\xed\xdb\xf3\xd3\xe7\x69\x59\xaf\x08\x33\xe9\x75\xd5\xaf\xd3\x7d\xbf\xfc\xef\xd9\xaa\xac\xcb\x36\xdf\x64\x8b\x8a\x91\xd2\x12\xc1\xa6\x84\x25\x19\xe2\x2c\xe9\xba\x0d\xb1\x28\x50\xc1\xe5\xe5\x9b\xf4\x8c\x29\x61\x97\xf7\x6b\x74\xa4\x5f\x27\xdd\x6f\x1b\x46\x94\x6b\xf0\x6a\x5d\xa6\x58\x0c\x00\x6a\x96\x43\xbc\xa4\x85\xf6\x75\x96\x94\x6d\x9b\x11\x07\xed\x6f\x18\xcd\x5a\xe7\x21\x68\xa9\x8e\xa8\xbd\x6e\x7a\x9a\xc6\x14\xe5\xa4\x8a\xaa\xfe\x94\x6f\xaa\x82\x90\xed\x11\x12\x97\x45\x62\xd1\xd0\xbc\x71\x69\xa2\xcc\xe6\x1a\x43\xcd\x17\xb4\x01\x74\xe9\xbd\xd9\x3d\x70\xdc\x7b\x8f\xef\xcd\x92\xba\xc9\x84\x4b\x30\x6f\x2e\xaa\x2e\x9f\x13\x9f\x96\x7d\xa3\x35\xae\xf7\x57\xa6\x1f\xe9\x8a\x42\xa4\x11\x04\xe3\x96\xf7\x22\x6c\x01\x4c\x5c\x39\x31\x6c\x30\x1b\x65\x33\xe1\xd8\x8d\x27\xb9\xf9\x15\xb6\xe4\x12\x46\x63\x4e\x6c\xc2\x8c\xba\x8e\x77\xbb\x4d\xb5\x90\xa6\x5f\x4a\x9e\x27\x34\xde\x99\x15\x29\x21\x1c\x08\xc5\xf2\x02\x72\xa1\x5e\x33\xdb\x4a\x23\x3e\x8f\xf2\xeb\x92\x56\xce\x7a\xbf\x92\xdd\x69\xd3\xec\x8b\x6f\xc0\x50\x6c\xe6\x3c\x3f\x49\xdf\x35\xd4\x61\x50\x87\x03\xf0\x4d\x1c\x13\x63\x60\x61\xa0\x2d\xb7\x4d\xcf\x88\xd3\x62\x15\x4d\xcf\x75\x45\x99\x34\xd2\x2e\xff\x44\x6c\xb1\x6f\x64\x49\x16\xb4\xe4\x16\x5c\x31\x71\xb0\x3d\xed\xe8\xb2\x2c\x88\x8f\xc8\xd2\xb0\xb4\x98\x06\x01\xb5\xdd\xd3\x6a\x5a\x53\x65\x8c\x78\x96\x48\xa8\xca\xa9\x7e\x62\x48\x54\x0f\x56\x39\xad\xdc\x86\x36\x4f\x9e\xe8\x53\x7c\xe8\xef\xb0\x7e\xea\x55\xbe\x5c\x52\xaf\x3a\x5a\x15\xaf\xd2\xc5\xa6\xa1\x25\xf5\xcb\xbb\x37\x1d\x2f\x98\x75\xb6\x6b\x5a\x48\x22\x94\x75\x41\x9f\x2e\x2d\x40\x34\x43\xd4\xfb\xed\x9c\x7e\x5d\xaf\x2b\x62\xd4\x40\x3b\x97\x60\x29\x89\x52\xa9\x89\x7d\x47\x53\x78\x94\xd2\x12\xa6\x1Line truncated
|
||||
var _confLocaleLocale_enUsIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xeb\x72\xdc\x48\xae\xe6\x7f\x3e\x05\xdb\x13\x0e\xdb\x11\x72\x39\xba\xfb\xec\x25\x3a\xda\xee\x95\xa5\xf6\x65\x8e\x65\x69\x24\xf5\xcc\xce\x3a\x1c\x6c\x56\x91\xaa\xe2\xb8\x8a\xac\x26\x59\x92\x35\x27\x4e\xc4\x3e\xc0\x3e\xc0\xee\xeb\x9d\x27\x59\xe0\x03\x90\x17\x92\x25\xd9\x3d\x13\xc7\x3f\x2c\x56\x26\xf2\x86\x44\x22\x91\x48\x00\x99\x6f\xb7\x59\x51\x76\x8b\xf4\x79\x7a\x98\x6e\xf3\xaa\x5e\x97\x5d\x97\x76\xe5\xfa\xea\xe9\xaa\xe9\xfa\xb2\x48\x5f\x57\x3d\xfd\x6e\xaf\xab\x45\x99\x24\xab\x66\x53\x12\xe8\x1b\xfa\x93\x14\x79\xb7\x9a\x37\x79\x5b\x50\xc2\xb1\x7d\x27\xe5\xe7\xed\xba\x69\x19\xe8\x67\xf9\x4a\x56\xe5\x7a\xcb\x65\xe8\x4f\xd2\x55\xcb\x3a\xab\x6a\xfa\x79\x41\x5f\xe9\xdb\x5a\x52\x9a\x5d\x6f\x49\xa7\xbb\x5e\xd2\x76\x5b\x4b\xfa\x65\x9b\xb4\xe5\xb2\xa2\xde\xb4\x94\x74\xae\x9f\xc9\x4d\x39\xef\xaa\x9e\x5b\xfa\x8b\x7c\x25\xd7\x65\xdb\x55\x0d\xd7\xfe\x67\xf9\x4a\xb6\xf9\x92\x01\xce\xe8\x4f\xd2\x97\x9b\xed\x3a\x47\x81\x4b\xfd\x4c\xd6\x79\xbd\xdc\x09\xcc\x3b\xfd\x4c\x16\x6d\x49\x59\x59\x5d\xde\x50\xea\x11\x7e\xcc\x66\xb3\x64\x47\x48\xc8\xb6\x6d\x73\x55\xad\xcb\x2c\xaf\x8b\x6c\x23\xc3\xfc\x85\xd2\x53\x4d\x4f\x29\x3d\xe5\x74\x0c\xa1\x2c\x68\xa8\x59\xde\xe9\x38\x08\x97\x34\xf2\xbc\x4b\x50\x55\x9d\x6f\xac\x34\x7f\x26\xe5\x26\xaf\xd6\x8c\xb5\xa7\xfc\x41\x1d\xef\xba\x9b\x06\xb8\x3d\xd3\x4f\x42\x42\xd6\xdf\x6e\x4b\xe0\xe0\xe9\x25\x7d\x25\x8b\x7c\xdb\x2f\x56\x39\xf7\x53\xbe\x12\x02\xda\x36\x84\x8c\xa6\xbd\x05\x9c\xfd\x48\x9a\x76\x99\xd7\xd5\xdf\xf3\x5e\x10\x74\x1a\xfc\x4c\x36\x55\xdb\x36\x8c\xdb\x13\x7c\x24\x34\xf4\x8c\xeb\xa1\x94\xf7\x84\x85\xa0\x16\xce\xd9\x54\xcb\x56\xd0\xc8\x99\x27\xf8\xc5\xb5\x70\xde\x55\xd3\x7e\xd2\x8c\x57\xfc\x39\x28\x4a\x9d\xd0\xdc\xb8\xfd\xbc\x26\xc4\x6b\xee\x09\x7e\x44\x00\x5d\x92\x17\x1b\x42\xe5\x36\xaf\x4b\xc6\xd1\x21\xff\x22\xbc\xd0\xaf\x24\x5f\x2c\x9a\x5d\xdd\x67\x5d\xd9\xf7\x55\xbd\x64\x64\x1f\x4a\x52\x7a\xa1\x49\x49\x90\xe7\xd2\x6e\x9b\x9d\x9b\x4e\x4a\xff\x2b\xfd\x4c\xcf\xe4\xa7\xe4\x05\x85\x90\xe9\x4a\xf2\x48\xba\xec\xaa\x2c\x0b\x19\x4b\x97\xbe\xa2\xef\x64\xbb\x5b\xaf\x09\x6b\xbf\xed\xca\xae\xe7\x42\x67\xf4\x9b\xc6\x2f\xbf\x93\xaa\xeb\xe8\x83\x92\xdf\xe2\x23\xa1\xa9\xab\x17\x18\xcc\x11\x3e\x92\xe4\x43\x57\xe6\xed\x62\xf5\x31\x91\xbf\xe8\x2b\x7f\x30\xed\xed\x9b\x54\x26\x24\x25\x22\x69\xc1\x1a\x48\x16\x4d\xc1\x3f\x8e\xe8\x0f\x55\x5d\xd5\x5d\x9f\xaf\xd7\x1f\x13\xfd\x60\x30\xf9\x92\x09\xe8\xab\x1e\x58\xd0\xc4\xf4\xa2\x2f\xb7\x1d\xcf\x60\xfa\xaa\x6a\xbb\xfe\x69\x5f\x11\xb1\x9e\xef\xea\xa4\x68\x16\x9f\x68\x19\xf0\x92\x46\xcb\x6f\xaf\x52\x42\xd6\x23\x5a\x08\xed\xae\xae\x09\x3d\xe9\xeb\x86\x50\x46\xcd\x54\xd4\xfe\x31\xa0\x0f\xd2\xed\xba\xcc\x3b\x02\x29\xf3\x22\xfd\x31\x4f\xfb\xbc\x5d\x96\xfd\xf3\x07\xd9\x9c\x96\xdf\xa7\x07\xe9\xaa\x2d\xaf\x9e\x3f\x78\xd8\x3d\x78\xf1\x7a\x47\xc5\xd6\x55\x5d\x76\x3f\x3e\xcb\x5f\xa4\x8b\x9c\x72\x08\x8d\xb7\xe9\xbc\xbc\xe2\xd5\x46\x6d\xa5\x44\xe5\xf5\x92\x57\xda\x6d\xbf\xe2\x06\x89\x12\xe8\xa3\x4b\x79\xa9\x7f\x93\xf0\x04\x10\x2b\xc8\x8a\xb9\xb1\x35\x74\x08\xc9\x2d\x4d\xc0\xc9\xed\xc5\x9f\xde\x1d\xa4\x67\xc4\xdb\x96\x6d\x89\x6f\xfa\x8f\x4a\x7c\x9f\xd2\x68\x2f\xab\xe3\x97\xb3\x84\xca\x1a\x42\x8e\xf3\x3e\x9f\x73\xdf\xdd\xec\x73\xa6\x2c\x42\x97\x87\xa5\xc8\xdc\x12\x9c\xb1\xeb\xa3\x69\x99\x5a\xc8\x54\x87\x2e\x7f\x57\xc7\x7b\xe6\x01\x94\xee\x30\x7b\x26\x38\xa3\xaa\xd2\xb7\xef\xdf\x9f\x1e\xbf\x4c\xcb\x7a\x49\x98\x49\x6f\xaa\x7e\x95\xee\xfa\xab\xff\x9e\x2d\xcb\xba\x6c\xf3\x75\xb6\xa8\x18\x29\x2d\x11\x6c\x4a\x58\x92\x21\xce\x92\xae\x5b\x13\x8b\x02\x15\x5c\x5c\xbc\x4b\x4f\x98\x12\xb6\x79\xbf\x42\x47\xfa\x55\xd2\xfd\xb6\x66\x44\xb9\x06\x2f\x57\x65\x8a\xc5\x00\xa0\xe6\x6a\x88\x97\xb4\xd0\xbe\xce\x92\xb2\x6d\x33\xe2\xa0\xfd\x2d\xa3\x59\xeb\xdc\x07\x2d\xd5\x11\xb5\xd7\x4d\x4f\xd3\x98\xa2\x9c\x54\x51\xd5\xd7\xf9\xba\x2a\x08\xd9\x1e\x21\x71\x59\x24\x16\x0d\xcd\x1b\x97\x26\xca\x6c\x6e\x30\xd4\x7c\x41\x1b\x40\x97\x3e\x98\x3d\x00\xc7\x7d\xf0\xf4\xc1\x2c\xa9\x9b\x4c\xb8\x04\xf3\xe6\xa2\xea\xf2\x39\xf1\x69\xd9\x37\x5a\xe3\x7a\x7f\x65\xfa\x91\xae\x28\x44\x1a\x41\x30\x6e\x79\x2f\xc2\x16\xc0\xc4\x95\x13\xc3\x06\xb3\x51\x36\x13\x8e\xdd\x78\x92\x9b\x5f\x61\x4b\x2e\x61\x34\xe6\xc4\x26\xcc\xa8\xeb\x70\xbb\x5d\x57\x0b\x69\xfa\xb5\xe4\x79\x42\xe3\x9d\x59\x91\x12\xc2\x81\x50\x2c\x2f\x20\x17\xea\x35\xb3\xad\x34\xe2\xf3\x28\xbf\x2a\x69\xe5\xac\x76\x4b\xd9\x9d\xd6\xcd\xae\xf8\x06\x0c\xc5\x66\xce\xf3\x93\xf4\xbc\xa1\x0e\x83\x3a\x1c\x80\x6f\xe2\x90\x18\x03\x0b\x03\x6d\xb9\x69\x7a\x46\x9c\x16\xab\x68\x7a\x6e\x2a\xca\xa4\x91\x76\xf9\x35\xb1\xc5\xbe\x91\x25\x59\xd0\x92\x5b\x70\xc5\xc4\xc1\x76\xb4\xa3\xcb\xb2\x20\x3e\x22\x4b\xc3\xd2\x62\x1a\x04\xd4\x66\x47\xab\x69\x45\x95\x31\xe2\x59\x22\xa1\x2a\xa7\xfa\x89\x21\x51\x3d\x58\xe5\xb4\x72\x1b\xda\x3c\x79\xa2\x8f\xf1\xa1\xbf\xc3\xfa\xa9\x57\xf9\xd5\x15\xf5\xaa\xa3\x55\xf1\x26\x5d\xac\x1b\x5a\x52\xbf\x9c\xbf\xeb\x78\xc1\xac\xb2\x6d\xd3\x42\x12\xa1\xac\x33\xfa\x74\x69\x01\xa2\x19\xa2\xde\x6d\xe6\xf4\xeb\x66\x55\x11\xa3\x06\xda\xb9\x04\x4b\x49\x94\x4a\x4d\xec\x3a\x9a\xc2\x83\x94\x96\x3Line truncated
|
||||
|
||||
func confLocaleLocale_enUsIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4379,12 +4379,12 @@ func confLocaleLocale_enUsIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_en-US.ini", size: 44506, mode: os.FileMode(420), modTime: time.Unix(1446167298, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_en-US.ini", size: 45505, mode: os.FileMode(420), modTime: time.Unix(1448170884, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_esEsIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\xbd\xcd\x6e\x1c\xc7\xb2\x27\xbe\x17\xa0\x77\x28\xeb\x42\xb0\x8d\x3f\xd5\x86\xed\xff\x7c\xc0\x70\xfb\x0c\x45\xd1\x96\x07\xfa\xe0\x15\x29\x1f\xdc\x31\x84\x76\x75\x57\xb2\x59\x56\x75\x55\x9f\xca\x2a\x52\xd4\xc5\x5d\xcc\x7e\x1e\x60\x30\x3b\x2d\xbd\xd0\xe2\xe2\xec\xbc\x39\x80\xf9\x26\xf3\x24\x13\xbf\x88\xc8\xaf\xaa\xea\xa6\xec\x73\x2e\x0c\x58\xec\xca\xc8\xaf\xc8\xc8\xc8\x88\xc8\x88\xc8\x7c\xbb\x5d\x14\xc6\xae\xe6\x2f\xeb\xcc\x9a\xf6\xb2\x5c\x95\x4d\x56\x98\xec\xbb\xb2\xcb\xf2\xbe\x6b\xb2\xbc\x6a\x7e\xce\x8b\x26\xbb\xce\x6c\x59\x67\xab\x66\xb3\xad\xca\x55\x4e\x50\xb5\xb1\x77\xef\xdc\xbd\x73\xd1\x6c\xcc\xfc\xfb\x1a\xf5\xee\xde\x29\x72\x7b\xb1\x6c\xf2\xb6\x98\x9f\xe4\xb5\xa9\xd0\xd0\xaa\xa9\xbb\xb6\xa9\xee\xde\x31\x6f\xb6\x55\xd3\x9a\xf9\x31\xff\x9b\xb7\x54\xd5\x54\xdb\xf9\xe1\x75\x5f\xe4\x77\xef\xd8\x72\x5d\x2f\xca\x5a\x5a\xca\x5b\x1a\x8b\x2d\x6f\xfe\x5a\x6b\x41\xd3\x77\xf3\x23\xd3\xb6\xa3\x82\x7e\x3b\x3f\xed\xed\xaa\x2d\xb7\x2b\xf9\xda\x9a\x75\x69\x3b\xd3\xce\x5f\xf0\x1f\x2d\x0d\xea\xca\x2c\x6d\xd9\x99\xf9\xc9\xcd\xbb\x75\x59\xe7\xd9\x9f\xcd\xf2\xee\x9d\x4b\xd3\x5a\x9a\xc3\xfc\x07\xfc\xcb\x35\xb7\xf9\xda\xc3\xdc\xbd\xd3\x19\x9a\x68\x8e\x5a\x55\x5e\x77\x65\x55\xd1\x37\xfa\x6b\xdd\x03\xea\xfb\xa2\x6c\x36\xf4\x61\xd5\x1a\x02\x59\xd4\xe6\x6a\x7e\x44\x7f\xb6\xb3\xd9\xec\xee\x9d\x9e\xd0\xb8\xd8\xb6\xcd\x79\x59\x99\x45\x5e\x17\x8b\x0d\x66\x7d\x62\x5a\xfa\x00\x84\xf4\xb6\xcf\xdb\x12\x08\xdd\xdc\xbc\xb3\x32\x0f\x53\xd0\xdc\x17\xb9\xa5\x96\x0d\xf5\x76\x4e\x18\x26\x94\x13\xb2\x1b\xa0\x18\x2d\xd6\x39\xa1\xf9\x59\xb3\x59\xb6\x26\x6a\x84\xb0\xba\xc9\xcb\x6a\x7e\xd4\xb4\xad\x69\x32\x53\x99\x55\xd7\xd2\x6c\xca\x55\x83\x09\x59\x7b\xd5\xd0\x5a\x1c\x61\x09\x72\x6b\x6e\xfe\x3d\x07\x82\x16\xdd\xf5\x16\x4b\xb6\x6e\x8d\xe5\xc6\xea\xde\x5c\x12\xfc\x2a\xdf\x76\xab\x8b\x7c\x7e\x24\xff\xa2\xe7\xd6\x6c\x1b\xc2\x5d\xd3\x5e\x13\x3e\xf5\x4f\xf4\xda\xb4\xeb\xbc\x2e\xdf\xe6\x1d\x50\xf8\x5c\x7f\xe8\x0a\x6c\xca\xb6\x6d\xda\xf9\x53\xfe\xe7\xee\x1d\x42\xce\x02\xcd\xcc\x9f\xa1\x97\xac\x8d\x9b\x41\xd9\xa6\x5c\xb7\xc0\x33\x8a\xf3\xec\x29\x7e\x69\x43\x28\x3d\x6f\xda\xd7\x5a\xf3\x5b\xfa\x93\x46\x5b\x65\x2f\x86\x4d\xd0\x68\xb4\x7a\x33\x18\x4a\x5e\xd3\x72\x71\xf9\x61\xb1\x29\x6b\x10\x04\x91\x50\x80\x12\x22\xce\x51\xb6\xd8\x82\x62\x03\xdd\xe6\xbe\x82\x36\x96\xaf\x56\x4d\x5f\x77\x0b\x6b\xba\xae\xac\xd7\x16\x68\x3d\x2f\xd7\x7d\xab\xed\xa0\x52\x95\x67\xab\x9e\x56\x10\x04\xbd\x03\xec\xee\x9d\xeb\xa6\xf7\x04\x32\x3f\xeb\xb3\x2d\x93\x86\x7e\xf7\xd5\xa8\x60\x15\x6a\xf2\x08\x78\xb6\x76\x71\x6e\x4c\x31\xff\x96\xfe\xc7\x6b\xd7\x74\xd8\x30\xd4\xec\xb6\xaf\x2a\xc2\xf4\x5f\x7a\x63\x3b\x3b\x3f\xa1\x5f\x84\x29\xf9\x75\xf7\x4e\x69\x2d\xfd\x45\x8b\xbe\x2a\x89\xc2\xa4\x02\x56\xbc\x5e\xd1\x9c\x8f\xf8\x1f\xec\xc8\xbb\x77\x7e\xb4\x44\xc7\xab\x8b\x57\x98\x00\xfe\x98\x3f\xa4\xed\xa5\x94\xbd\x8b\x1a\x40\x9f\xf3\x97\x8e\x22\xb9\xab\xa8\x27\xea\xa6\x29\xcc\xfc\xe8\xe6\xaf\x45\xb9\x66\x7a\xfe\xb1\xac\x6d\x97\x57\x15\x75\xa2\x7f\x11\x38\xfe\x75\x13\xed\xca\x8e\x50\x73\x92\xdb\xc6\x61\xb5\x8c\xca\xb3\x6d\xd3\x66\xdb\xb6\xdc\x98\x36\xcf\x2e\xcd\x5b\x62\x3b\xcd\xea\x35\x6d\x3a\xf0\x13\x1a\xc9\x69\x99\xd1\xa4\x6f\xde\x65\xe6\x67\xb3\xea\x3b\xda\x82\x4d\xf6\x5d\xb3\xb6\xb4\x69\xf8\xef\x47\x0c\x7d\xc0\xcd\x9c\xe7\x97\xf4\xff\xca\xe4\xd9\xd7\x79\xd6\xe5\xed\xda\x74\xf3\x7b\x8b\x25\xed\xf4\xd7\xf7\xb2\x8b\xd6\x9c\xcf\xef\xdd\xb7\xf7\xbe\x41\x83\xb9\xcd\xb6\xc4\x11\x73\xfb\xf5\x67\xf9\x37\x19\x31\x05\x59\xf2\x55\xbe\x59\x82\x61\xd5\x79\x91\x67\xa6\x66\xc8\x6c\x2b\x6c\xe4\x23\xe0\xec\x2f\x3d\x31\x9f\x45\xb1\x14\x36\xcb\x03\xe1\x8f\x86\x76\x72\x4f\xec\x68\x99\xcb\x2e\x2c\xf2\x8e\xa6\xfb\xf4\xfa\xf4\x9f\x9f\x1c\x64\x27\x8d\xed\x68\x7f\xf2\xdf\xf4\x3f\x6a\xe1\xcb\xac\xc9\xce\xca\x47\x0f\x69\x1d\xa8\x2d\xc1\xd0\x51\x42\x20\x68\x24\x69\x4c\x20\xb1\xd9\xcf\xca\x6d\x33\x51\x7c\x41\xbd\xcc\x1f\xd3\xff\x86\x6b\x38\xcd\x3a\xa8\xb5\x01\x1b\xaa\xf2\x89\x1e\x75\x19\x4e\x3c\x7a\x7b\xe2\x9f\xe5\xca\x10\x7b\xca\x36\x0d\xd1\x4c\xf6\xfd\xb3\x67\xcf\x1f\x3d\x04\x7d\xf3\x8e\x19\xcd\x82\x08\x2e\x5f\x11\x13\x27\x0c\xf7\xdd\xf9\x7f\x5d\xac\x4d\x4d\x6b\x5d\x2d\x56\x25\x2d\x01\x2d\x3a\x23\x89\x10\x61\x6d\x45\xdc\x95\x88\xeb\x69\x43\xeb\x7a\x7a\xfa\x04\x23\xef\x2e\xe6\x2f\x7a\xde\x81\x7f\xa9\x80\x79\x1d\x0e\xbe\x31\xfb\x00\x55\x97\x97\x8d\xeb\x3d\x45\xff\x08\xd7\x74\xe8\x2c\xe8\x28\xe8\xae\xb1\x82\xdc\xf8\x93\x3c\x6b\xd1\x56\x7e\x5b\x6d\xda\x9b\xd9\xb6\x37\x54\x0a\x9a\x68\xb3\xcb\x7c\x75\xf3\x3e\xd7\x36\xcb\xfa\x32\xaf\xca\x82\x16\xd2\x61\xf5\xb8\xa2\x0a\xbb\x10\x3b\x68\x10\x87\x2a\x70\x92\x55\x54\x14\x61\xeb\xde\xec\x5e\x56\x97\xd9\xbd\x07\xf7\xa8\x9b\xba\x59\x08\x67\xc3\x21\x54\x94\x36\x5f\xd2\x81\x24\xe7\x63\x2b\x9c\xfb\x99\x6b\x8f\x48\xf3\x22\x5f\xd2\x2a\x61\x9c\x84\x23\x85\x6a\xe4\xcc\xc7\xd1\xc6\xa4\x2a\xbc\x2d\xe5\x8e\x45\xd3\x26\x68\x72\xcc\x54\x09\xe8\x49\x2e\x12\x80\xd0\xd0\xa8\xea\x4e\x1c\xdd\xbd\xe3\x16\x7d\x92\xd4\xbf\x93\x42\x96\x54\x68\x47\x11\x77\x26\x39\x66\x4c\x9c\x87\x2a\xac\x08\x03\x57\x90\x40\xa0\x75\x96\xff\xa5\xbLine truncated
|
||||
var _confLocaleLocale_esEsIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x7d\x4f\x6f\x1c\x47\xb2\xe7\xbd\x3e\x45\x59\x03\x41\x36\x96\x6a\xc1\xf6\xdb\x3f\x30\xdc\xf6\x52\x14\x6d\x79\x21\x51\x7c\x22\xe5\xc1\x5b\x43\x68\x67\x77\x25\x9b\x65\x55\x57\xf5\x54\x56\x91\x6a\x3d\xbc\xc3\xde\xf7\x03\x2c\xf6\xa6\xa3\x0f\x3a\x3c\xcc\x6d\x2e\x03\x98\xdf\x64\x3f\xc9\xc6\x2f\x22\xff\x56\x55\x93\xf2\xcc\x3c\x1f\x2c\x76\x56\xe4\xbf\xc8\xc8\xc8\x88\xc8\x88\x48\xb5\xdd\x2e\x0a\x6d\x56\xf3\x57\x75\x6e\x74\x7b\x55\xae\xca\x26\x2f\x74\xfe\x7d\xd9\xe5\xaa\xef\x9a\x5c\x55\xcd\x2f\xaa\x68\xf2\x5d\x6e\xca\x3a\x5f\x35\x9b\x6d\x55\xae\x14\x41\xd5\xda\x64\xd9\x65\xb3\xd1\xf3\x1f\x6a\xd4\xca\x0a\x65\x2e\x97\x8d\x6a\x8b\xf9\xa9\xaa\x75\x85\x56\x56\x4d\xdd\xb5\x4d\x95\xe9\xb7\xdb\xaa\x69\xf5\xfc\x98\xff\x55\x6d\x76\xa9\xab\xed\xfc\x70\xd7\x17\x2a\x33\xe5\xba\x5e\x94\xb5\xb4\xa2\x5a\x1a\x85\x29\x6f\xfe\x5c\x4b\x79\xd3\x77\xf3\x23\xdd\xb6\xc3\xf2\x7e\x3b\x3f\xeb\xcd\xaa\x2d\xb7\x2b\x2e\x6c\xf5\xba\x34\x9d\x6e\xe7\x2f\xf9\x8f\xb6\xc9\xae\xf5\xd2\x94\x9d\x9e\x9f\xde\xbc\x5f\x97\xb5\xca\xff\xa8\x97\xd9\x95\x6e\x0d\x0d\x7c\xfe\x23\xfe\x45\xb5\xad\x5a\x7b\x88\xac\xd3\x34\x37\x85\x2a\x95\xaa\xbb\xb2\xaa\x54\x46\x7f\xac\x7b\xc0\xfc\x50\x94\xcd\x46\x65\xab\x56\x13\xc0\xa2\xd6\xd7\xf3\x23\xfa\xb3\x9d\xcd\x66\x59\x4f\x68\x5b\x6c\xdb\xe6\xa2\xac\xf4\x42\xd5\xc5\x62\x83\x99\x9e\xea\x96\x0a\x80\x83\xde\xf4\xaa\x2d\x81\xc0\xcd\xcd\x7b\xc3\xc3\xd7\x05\x4d\x78\xa1\x0c\x35\xab\xa9\xa7\x0b\x42\x28\x61\x98\x70\xdb\x64\xdc\x5c\xad\x08\xab\x27\xcd\x66\xd9\xea\xa8\x85\x4c\x6f\x54\x59\xcd\x8f\x9a\xb6\xd5\x4d\xae\x2b\xbd\xea\x5a\x9a\x44\xb9\x6a\x68\x1e\xc6\x5c\x37\x84\xf9\x23\x20\x5c\x19\x7d\xf3\xef\x8a\x70\xb2\xe8\x76\x5b\x2c\xcf\xba\xd5\x86\x5b\xaa\x7b\x7d\xd5\x64\x2b\xb5\xed\x56\x97\x6a\x7e\x24\xff\x66\x04\xb9\x6d\x08\x59\x4d\xbb\x23\xfc\xd9\x3f\xa9\xbf\xa6\x5d\xab\xba\x7c\xa7\x3a\xe0\xec\x85\xfd\x21\xf8\xde\x94\x6d\xdb\xb4\xf3\xe7\xfc\x4f\x46\xf8\x58\xa0\x8d\xf9\x09\xda\xcf\xdb\xa8\x0d\x7c\xda\x94\xeb\x16\x78\xc5\x57\x95\x3f\xc7\x2f\x69\x05\x1f\x2f\x9a\xf6\x8d\xad\xf7\x1d\xfd\x49\xa3\xac\xf2\x97\x83\x06\x68\x20\xb6\x72\x93\x8e\x42\xd5\xb4\x38\xfc\xf9\xb0\xd8\x94\x35\x56\x9e\x28\x25\x00\x31\x91\x2a\x7c\x5a\x6c\x41\x93\x81\x32\x95\x87\x97\xa6\xd4\x6a\xd5\xf4\x75\xb7\x30\xba\xeb\xca\x7a\x6d\x80\xc9\x8b\x72\xdd\xb7\xb6\x15\xd4\xa9\x54\xbe\xea\x69\xc1\x88\x66\xf7\x40\x65\xbb\xa6\xf7\xc4\x30\x3f\xef\xf3\x2d\x93\x81\x14\xfb\x4a\x54\xbe\x0a\xf5\xd0\x3b\x66\x69\x16\x17\x5a\x17\xf3\xef\xe8\x7f\xbc\x54\x4d\x87\xfd\x60\xb2\x6d\x5f\x55\x84\xdd\x3f\xf5\xda\x74\x66\x7e\x4a\xbf\x08\x3f\xf2\x2b\x2b\x8d\xa1\x3f\x68\x85\x57\x25\x51\x12\x83\xd3\xea\xd6\x2b\x9a\xe9\x11\xff\x43\x5b\x2d\xfb\xc9\x10\xa5\xae\x2e\x5f\x67\xf2\xef\xfc\x31\x6d\x1c\x21\xdd\x3d\xeb\x0e\x12\x9c\xbf\xb2\x44\xc7\x5d\x44\x3d\x64\xab\xa6\xd0\xf3\xa3\x9b\x3f\x17\xe5\x9a\xc8\xf5\xa7\xb2\x36\x9d\xaa\xaa\xd7\x99\xfd\x83\x40\xf1\xaf\x9d\x58\x57\x76\x84\x88\x53\x65\x1a\x87\xc1\x32\xfa\x9c\x6f\x9b\x36\xdf\xb6\xe5\x46\xb7\x2a\xbf\xd2\xef\xb2\xa2\x59\xbd\xa1\xdd\x04\xee\x40\x43\x38\x2b\x73\x9a\xe5\xcd\xfb\x5c\xff\xa2\x57\x7d\x47\x7b\xab\xc9\xbf\x6f\xd6\x86\x36\x04\xff\xfd\x84\xa1\x0f\xb8\x95\x0b\x75\x45\xff\xaf\xb4\xca\xbf\x56\x79\xa7\xda\xb5\xee\xe6\xf7\x16\x4b\xda\xc0\x6f\xee\xe5\x97\xad\xbe\x98\xdf\xbb\x6f\xee\x7d\x83\x06\x95\xc9\xb7\xc4\xda\x94\xf9\xfa\x91\xfa\x26\xa7\xad\x2e\xab\xbb\x52\x9b\x25\xf8\x4f\xad\x0a\x95\xeb\x9a\x21\xf3\xad\xf0\x86\x4f\x32\x2c\x00\xb1\x93\x45\xb1\x14\x76\xc9\xe3\xe0\x42\x4d\x9b\xb4\x27\x06\xb3\x54\xb2\xc7\x0a\xd5\xd1\x64\x9f\xef\xce\xfe\xf9\xd9\x41\x7e\xda\x98\x8e\x76\x1f\xff\x4d\xff\xa3\x16\xbe\xcc\x9b\xfc\xbc\x7c\xf2\x78\x96\x51\x53\x82\x9e\xa3\x84\x18\xd0\x46\xd2\x16\x03\x62\x23\x9f\x97\xdb\x66\xfc\xf5\x92\xba\x98\x3f\xa5\xff\xa5\xeb\x36\xc9\x12\xa8\xa5\x01\x6f\xa9\xd4\xb8\x33\x8b\xfe\x53\x8f\xd6\x9e\xb8\x61\xb9\xd2\xc4\x73\xf2\x4d\x43\x34\x92\xff\x70\x72\xf2\xe2\xc9\x63\x50\x31\x6f\x8a\xd1\xf8\x89\xbe\xd4\x8a\xd8\x31\x61\xb6\xef\x2e\xfe\xdb\x62\xad\x6b\x5a\xe2\x6a\xb1\x2a\x09\xf5\xb4\xd6\x8c\x9d\x59\x66\x4c\x45\xdc\x92\xc8\xe9\x79\x43\xcb\x79\x76\xf6\x8c\x46\xdd\x5d\xce\x5f\xf6\xd8\x62\x7f\xaa\x80\x6f\x3b\x16\x14\x31\x6b\x00\x05\x97\x57\x8d\xeb\x3a\x45\xfa\x10\xc3\x74\x70\x2c\x88\xa9\x77\x3b\x2c\x1b\x37\xfd\x4c\xe5\x2d\x9a\x52\x77\x55\xa6\xfd\x97\x6f\x7b\x4d\x5f\x41\x07\x6d\x7e\xa5\x56\x37\x1f\x94\x34\x59\xd6\x57\xaa\x2a\x0b\x5a\x3d\x87\xcf\xe3\x8a\xe0\xf7\xa1\x74\xd0\x1e\xce\x44\xa0\x23\xaf\xe8\x53\x84\xa8\x7b\xb3\x7b\x79\x5d\xe6\xf7\x1e\xde\x9b\x65\x75\xb3\x10\xae\x85\xf3\xa4\x28\x8d\x5a\xd2\xd9\x22\x67\x5c\x2b\xec\xf8\xc4\x35\x47\xd4\x78\xa9\x96\xb4\x3e\x18\x25\x21\xc8\x42\x35\x72\x5c\xe3\x90\x62\xea\x14\xc6\x95\x72\xbe\xa2\x69\x63\x1c\x39\x3e\x69\xe9\xe6\x99\x92\xe3\x5b\x48\x67\x54\x73\x1f\x82\x32\xb7\xd6\x93\xb4\xfd\xbd\x7c\x64\x09\x83\x76\x50\x06\xf1\x63\x4c\x91\x87\x56\xc6\x60\xc6\x6c\x21\x02\x55\xd6\xb9\xfa\x53\x7f\xf3\x01\x93\x0d\x48\xef\xfa\xf4\x6c\x38\xc8\x7f\x7b\xaf\xaa\x0e\xc7\xee\x8a\xb8\x60\xf3\x09\x33\xbb\x85\xa7\x2f\x26\xa6\xe8\xa0\x42\x1b\x2f\x55\xf9\x2e\xfLine truncated
|
||||
|
||||
func confLocaleLocale_esEsIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4399,12 +4399,12 @@ func confLocaleLocale_esEsIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_es-ES.ini", size: 49108, mode: os.FileMode(493), modTime: time.Unix(1446027988, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_es-ES.ini", size: 49106, mode: os.FileMode(493), modTime: time.Unix(1448150136, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_frFrIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xdd\x8e\x1c\xc7\x72\xe6\x3d\x01\xbe\x43\x1d\x1a\xdc\x21\x81\x61\x0b\x92\xf6\x0f\x82\x5a\x5a\x72\x48\x49\x3c\x18\x52\x3c\x1c\x92\xc6\xae\x20\xb4\x6a\xba\x72\x7a\x4a\xac\xae\x6a\x55\x56\x0d\x39\x34\x0c\xec\xed\x01\xf6\x01\xf6\xd2\xf0\xc5\xae\x68\x03\x7b\xe5\x3b\xdf\x9d\x79\x13\x3f\xc9\xc6\x17\x11\xf9\x5b\xd5\x43\x4a\x6b\x2f\xec\x23\x4e\x67\xe5\x6f\x64\x64\x64\xfc\x67\xb9\xdb\xad\x2a\x63\xd7\xcb\x97\x6d\x61\x4d\x7f\x51\xaf\x4d\x51\x99\xe2\xdb\x7a\x28\x6c\xd9\xda\x62\xd7\xd7\x96\x4b\x86\xab\x7f\x18\x4c\x51\x8e\x43\x77\xef\xfc\xea\xfd\xa9\xe9\x37\x57\xef\x8b\x75\x57\xd1\x7f\x4d\x5b\x7c\xdb\xdd\xbc\x71\xf3\xc6\x79\xb7\x35\xcb\xfb\xeb\xf5\x68\xea\xe6\xe6\x8d\xaa\xb4\xe7\xa7\x5d\xd9\x57\xcb\x17\xe5\x69\x63\xca\x11\xdd\x9c\x76\x7d\x75\xf3\x86\x79\xbb\x6b\xba\xde\x2c\x1f\xc9\xbf\x3d\x35\x35\xcd\x6e\x79\xbf\xae\xcc\xcd\x1b\xb6\xde\xb4\xab\xba\x5d\x1e\x75\x6d\x6b\xde\xd6\x5d\xab\x45\xdd\x38\x2c\x1f\x5e\xbd\x5f\x67\xc5\xe3\x6e\x79\xd4\x5f\xbd\x37\x7d\x31\xb6\x34\xa1\xed\x6e\xa0\x3e\x7a\xb3\xa9\xed\x60\xfa\xe5\xc9\x41\xdd\xda\x75\x5f\xf7\x54\xf8\xc6\x9c\xda\x7a\x30\xcb\x13\xfa\x4f\xf1\xd7\xe6\xf4\xe6\x8d\x0b\xd3\x5b\xea\x69\xf9\x4a\xfe\xbd\x79\x63\x57\x6e\xcc\xf2\x19\xfd\xe7\xe6\x8d\xc1\x6c\x77\x4d\x49\xd5\x9f\xd0\x22\x7f\x6d\xa8\xa4\x29\xdb\xcd\x88\x0a\xc7\xf8\x83\x0a\xd6\xbd\xa1\x0a\xab\xd6\xbc\xd1\x29\x2c\x16\x8b\x9b\x37\x46\x82\xe3\x6a\xd7\x77\x67\x75\x63\x56\x65\x5b\xad\xb6\x58\xea\x33\x2e\x28\xc6\xa1\x6e\x6a\x4b\xad\xc6\xbe\x30\x43\xb1\x6b\x46\x2b\xeb\x30\x15\xad\x79\x55\x5a\x59\xf6\x7a\x10\xb8\x0e\x65\x3b\x14\xbf\x60\x2c\xe9\xb7\x2d\x09\xc2\x4f\xbb\x6d\x51\x1d\x44\x3d\x11\x40\xb7\x65\xdd\x2c\x1f\xdd\xc3\x3f\x58\x85\xb5\x6f\x08\xd0\x34\xf5\x01\x40\xc7\x6f\x06\xca\x6a\xb8\xdc\x19\x8c\x70\x56\xf7\x5b\xf3\x8e\x56\x50\xee\x86\xf5\x79\xb9\x3c\x92\x7f\x31\x4c\x6f\x76\x1d\x81\xa9\xeb\x2f\x01\xed\xdd\xd5\x3f\x0d\x37\x6f\x74\xfd\xa6\x6c\xeb\x77\xe5\x00\x60\x7d\xcf\x3f\x2c\xff\xb8\x79\x63\x5b\xf7\x7d\xd7\x2f\x9f\xd4\x7d\x57\xd3\x44\x08\x16\x2b\xf4\x40\x93\x1c\x2f\xb0\xe7\xbe\x0f\x7c\xd9\xd6\x9b\x1e\x20\xe5\x8f\x4d\x63\x8a\x27\x5c\xc0\x1d\xe1\xfb\x59\xd7\xbf\xd6\x8f\x85\xd9\x9e\xf6\x65\xbb\x3e\x37\x5b\xd3\x6a\x73\x9a\x46\x68\x9a\x4d\xa3\x6c\x69\x67\xb8\xc6\xb7\x57\xef\x09\xa5\x8a\xc6\xd8\xa4\x0e\xc1\xb9\xac\xb6\x04\xe3\x5d\xd9\x9a\x66\x79\x1f\x7f\x13\x8e\xb8\xe1\xcb\xf5\xba\x1b\xdb\x61\x65\xcd\x30\xd4\xed\xc6\x12\x16\xf4\xe5\xf6\xea\xd7\xa1\xa7\x7e\xaa\xb1\x38\x52\xcc\x9a\xfb\x7e\xf3\xc6\x65\x37\xfa\x3d\x5f\xbe\xea\xa8\xb0\x90\x5f\xfa\xc9\xb7\x7a\xd5\xd1\x99\x8a\x5b\xf2\xca\xec\xea\xcc\x98\x6a\xf9\x4d\x33\xbe\xa5\x9d\x2d\xd7\xc3\x58\x36\x35\xa1\x00\x7d\xdf\x8d\x4d\x43\x10\x25\x1c\xb0\x03\x0d\x4a\xbf\x8a\xe7\xfa\xeb\xe6\x8d\xda\x5a\xfa\x0b\xb8\x75\xda\x5c\xfd\xba\x95\xfe\xd6\x04\x35\x2c\xb0\x6d\xc7\x06\x47\xeb\xe6\x8d\x1f\xac\x29\xfb\xf5\xf9\x8f\x98\x3d\xfe\x58\x3e\x37\x04\xd7\x1e\xff\x63\x8c\x9d\xdb\x72\x60\xdb\xf2\x65\x8c\x63\x3c\x58\x18\x8b\x06\xea\x2a\x20\x53\xc5\xd8\xf9\x03\x9d\xb3\xa1\x6c\x1a\x1a\x44\xff\x5a\x3e\x96\x7f\x15\xc0\x43\x3d\x10\x6c\x50\xd6\x8f\x6b\xde\x90\x62\x47\xa0\x29\x9a\x92\x40\x65\xb6\xf5\xd5\xaf\x04\x34\xf3\x96\x0e\xf8\x28\x0d\xaa\x6e\xfd\x9a\x4e\x12\x28\x03\x0e\x72\x5d\x5c\x74\xa3\x75\x35\xe8\x78\x7c\xdb\x6d\x6c\xb1\xe9\xaf\xfe\x9e\x48\xd6\xd5\xdf\x15\x0f\xb9\xfa\x61\xb1\xa5\x85\xd5\xc0\xfa\x86\x0e\x3d\x7a\xff\xb2\xa4\x63\xd4\x6f\xcc\xb0\xbc\xb5\x3a\xa5\x33\xfc\xfa\x56\x71\xde\x9b\xb3\xe5\xad\xdb\xf6\xd6\x57\xb4\x4b\xeb\xab\xf7\xd5\xd8\x9b\x2f\x3f\x29\xbf\x2a\xca\x61\x20\x6c\xab\x2f\x18\xe9\x8a\xf2\x02\xc7\x8f\xba\xda\x76\x55\x7d\x56\x13\x52\xfd\x32\x76\x35\xce\x63\x41\x63\xda\x8e\x88\x64\x05\x22\xb9\xa6\xdd\xc5\x29\xdb\x98\xe2\x0f\x80\xe6\x2f\x23\x51\x98\x55\x75\x2a\x94\x95\xe7\xc9\x85\xa6\x1f\x8a\x27\x97\x27\x7f\x3a\x3e\x2c\x9e\x75\x76\xd8\xf4\x86\xff\xa6\xff\x50\xfd\xcf\x8b\x6e\x2c\x5e\xd4\x0f\x1f\xd0\x86\x50\x53\x01\x57\x82\x84\xbc\x9a\xd3\x52\xe8\x71\x45\x64\x82\x48\x8e\x95\xca\x38\xd6\x2f\xe8\x3f\xf8\xf2\x40\x6b\x3c\xf4\x35\xce\x69\xb0\xe5\x77\xb4\xad\x66\x6e\x5f\x13\x6a\xf1\xd0\x14\xcf\x84\x5a\x50\xb7\x81\xda\xf0\xd0\xd3\x8e\xa9\x8e\x6e\xcf\x2b\x33\xd6\x74\x2a\xdf\x29\x81\xe3\xf3\x47\x60\x63\x3a\xf7\xf8\xe9\xd3\xef\x1f\x3e\x20\x68\x9a\x35\x4a\x7f\x36\x7c\x13\xac\x69\x69\x44\xe6\x7e\xc5\xd2\xc6\xe1\xec\x3f\xaf\x36\xa6\x35\x7d\xd9\xac\x68\xf7\x18\x31\x18\x52\x04\x0c\x6b\x1b\x22\xa1\x15\x13\x62\x53\x9c\x9c\x1c\x63\xca\xc3\xf9\xf2\x88\x48\x43\x8d\x5b\xe0\x97\x06\xe0\xd6\x89\x1c\x53\xcf\xfc\x01\x87\xf6\xac\x5e\x9f\x63\xdb\xe6\x41\x37\x03\x78\xd3\xf7\x2b\xa2\xfb\xc3\x25\x76\x8f\x47\x89\xfa\xfb\xa8\x4e\x8a\x96\x30\xc1\x8c\x43\x41\xf7\x25\xe1\xdf\x05\x5d\x69\xda\x6f\xdd\x5e\xd0\xa9\xae\x68\x67\x1d\x68\xa9\xef\xd6\x43\x77\xd2\xf1\x8b\xc7\xbe\xb7\xb7\x74\xb1\xb5\x84\x99\xb5\x90\xb5\x18\x74\xb7\x16\xb7\x30\xf4\xad\x7b\xb7\x68\x98\xb6\x5b\x09\x8d\xc3\xb5\x53\xd1\x0e\xd3\xbd\xbb\x92\xbb\x50\xe8\x1c\xd1\x1f\x3a\x43\xe8\x14\x64\xf4\x1d\x6e\x86\x82\xae\x70\x4b\xdd\x11\xd2\x53\xef\x07\xa6\xd5\xfa\x72\x04\x98\x09\x58\x67\xfLine truncated
|
||||
var _confLocaleLocale_frFrIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xdd\x8e\x1c\x47\x76\xe6\x7d\x3d\x45\x8a\x03\xa2\x49\xa0\x59\x84\x24\xef\x0f\x06\x2a\xcd\x36\x9b\x94\xc4\x41\x93\xea\x61\x53\xf4\x62\x05\xa2\x94\x5d\x19\x55\x9d\x64\x56\x66\x4d\x46\x66\x37\x9b\x86\x81\xbd\x1d\x60\x1f\x60\x2f\x07\xbe\xd8\x15\x6d\xc0\x57\xbe\xf3\x9d\xfa\x4d\xfc\x24\x7b\xbe\x73\x4e\xfc\x65\x66\x35\x29\xc1\x5e\xc3\x23\x76\x45\xc6\xef\x89\x13\x27\xce\x7f\xe4\xbb\xdd\xb2\x30\x76\xb5\xf8\xa1\xce\xac\x69\x2f\xcb\x95\xc9\xbe\x2d\xbb\x2c\xef\xbb\xe6\xc1\xc5\xcd\x87\x73\xd3\x6e\x6e\x3e\x64\x36\xaf\x6d\xb6\x6b\x4b\x6b\xb2\xc2\x64\xdd\xcd\x3f\x76\x66\x36\xbb\x68\xb6\x66\x71\xb4\x5a\xf5\xa6\xac\x66\x45\x6e\x2f\xce\x9b\xbc\x2d\x16\x2f\xf3\xf3\xca\xe4\x3d\x2a\x9e\x37\x6d\x31\x33\xef\x76\x55\xd3\x9a\xc5\x13\xf9\xb7\x9d\x5d\x98\x6a\xb7\x38\x2a\x0b\x33\xb3\xe5\xa6\x5e\x96\xf5\xe2\xb8\xa9\x6b\xf3\xae\x6c\x6a\x29\x69\xfa\x6e\xf1\xf8\xe6\xc3\x2a\x2d\xed\x77\x8b\xe3\xf6\xe6\x83\x69\xb3\xbe\xce\x56\xcd\x76\x47\x73\x68\xcd\xa6\xb4\x9d\x69\x17\x4f\x6b\xbb\x6a\xcb\x5d\x87\xda\x57\xe6\xdc\x96\x9d\x59\x9c\xd1\x7f\xb2\xbf\x35\xe7\xb3\x4b\xd3\x5a\xfa\xb0\x78\x25\xff\xce\x76\xf9\xc6\x2c\x4e\xe9\x3f\xb3\xce\x6c\x77\x55\x4e\x75\x9f\x35\xc5\xcd\xcf\x95\x99\x55\x79\xbd\xe9\xf1\xf9\x04\x7f\x98\xd9\xaa\x35\xf4\x79\x59\x9b\x2b\x1d\x7c\x3e\x9f\xcf\x7a\x82\xd4\x72\xd7\x36\xeb\xb2\x32\xcb\xbc\x2e\x96\x5b\xac\xef\x94\x0b\xb2\xbe\x2b\xab\xd2\x52\xa3\xbe\xcd\x4c\x97\xed\xaa\xde\xf2\xfc\x4d\x41\x2b\x5d\xe6\x56\x16\xbb\xea\x08\xaa\xa6\xce\xba\xbc\xee\xb2\x3f\xd3\x40\xdc\x69\x9d\x13\x44\x9f\x37\xdb\xac\x38\x88\xba\x99\x99\x6d\x5e\x56\x8b\x27\x0f\xf0\x0f\x4d\xde\xda\x2b\x82\x2b\x4d\xb9\x03\x8c\xf1\x1b\x80\x58\x76\xd7\x3b\x83\xce\xd7\x65\xbb\x35\xef\x67\xab\x7c\xd7\xad\x2e\xf2\xc5\xb1\xfc\x3b\xa3\x2a\xbb\x86\xe0\xd2\xb4\xd7\x00\xee\xee\xe6\x5f\xba\x59\xd3\x6e\xf2\xba\x7c\x9f\x03\x6c\x8b\xef\xf9\x87\xe5\x1f\xb3\x6d\xd9\xb6\x4d\xbb\x78\x56\xb6\x4d\xd9\xce\x68\xf9\x4b\x34\xa7\xb9\xf5\x97\xd8\x5b\xd7\x01\x3e\x6c\xcb\x4d\x0b\x10\xf2\xb7\xaa\x32\xd9\x33\x2e\x40\x2f\xf8\xbc\x6e\xda\xb7\xfa\x2d\x33\xdb\xf3\x36\xaf\x57\x17\x66\x6b\x6a\x69\x4c\x33\x08\x0d\xd3\x19\xe4\x35\xed\x03\x57\xf8\xf6\xe6\x03\xe1\x4d\x56\x19\x9b\x54\xb1\xb3\xbc\xd8\x12\x4c\x77\x79\x6d\xaa\xc5\x11\xfe\x26\x64\xd0\xa1\xf3\xd5\xaa\xe9\xeb\x6e\x69\x4d\xd7\x95\xf5\xc6\xd2\x7e\xb7\xf9\xf6\xe6\xe7\xae\xa5\x5e\x8a\x3e\x3b\x16\x04\x9a\xfa\x3c\xbb\x6e\x7a\xbf\xbf\x8b\x57\x0d\x95\x65\xf2\x4b\xbe\xf8\x36\xaf\x1a\x3a\x14\x51\x3b\xac\xc8\x2e\xd7\xc6\x14\x8b\x6f\xaa\xfe\x1d\xed\x62\xbe\xea\xfa\xbc\x2a\x69\xb3\xed\x6c\xd7\x57\x15\x41\x91\x36\xdb\x76\x34\x1e\xfd\xca\x5e\xe8\xaf\x59\x69\x2d\xfd\x01\x14\x3a\xaf\x6e\x7e\xde\xa2\xaf\x15\x01\x0a\xcb\xaa\xeb\xbe\xa2\x43\x33\xfb\xd1\x9a\xbc\x5d\x5d\xbc\x9e\xc9\xbf\x8b\x17\x86\xe0\xd8\xe2\x7f\x40\xc9\x89\xcd\x05\x42\x2d\x7e\x88\xd0\x88\x07\x09\x63\xcc\x56\x4d\x01\x84\xa1\x73\x38\xfb\xb1\xac\x6d\x97\x57\xd5\xeb\x99\xfe\x81\x03\x85\x7f\x05\x98\x5d\xd9\x11\x24\x50\xd4\xf6\x2b\x06\x7d\xb6\x23\x48\x64\x55\x4e\x80\x31\xdb\xf2\xe6\x67\x02\x91\x79\x47\x47\xb6\xe7\xfa\x45\xb3\x7a\x4b\x47\x04\x07\x9d\xe6\x70\x56\x66\x97\x4d\x6f\x5d\x05\xf3\x3e\xfb\xb6\xd9\xd8\x6c\xd3\xde\xfc\x03\x51\x9b\x9b\xbf\x66\x8f\xb9\xfa\x61\xb6\xa5\xf5\x94\xc0\xe9\xaa\xa4\xfe\xa8\xf3\xaf\x72\x3a\x20\xed\xc6\x74\x8b\x3b\xcb\x73\x3a\x9a\x6f\xef\x64\x17\xad\x59\x2f\xee\xdc\xb5\x77\xbe\xa6\x2d\x59\xdd\x7c\x28\xfa\xd6\x7c\xf5\x30\xff\x3a\xcb\xbb\x8e\x90\xaa\xbc\x64\xdc\xca\xf2\x4b\x1c\x2c\xea\x6a\xdb\x14\xe5\xba\x24\xec\xf9\x73\xdf\x94\x38\x69\x19\x8d\x69\x1b\x22\x71\x96\x16\xb0\xa2\xad\xc4\x11\xda\x98\xec\xb3\x19\xf6\x86\x48\xc6\xb2\x38\x17\x92\xc8\xb3\xe4\x42\xd3\x76\xd9\xb3\xeb\xb3\x3f\x9d\x1c\x66\xa7\x8d\xed\x36\xad\xe1\xbf\xe9\x3f\x54\xff\xcb\xac\xe9\xb3\x97\xe5\xe3\x47\xf3\x19\xb5\x14\x50\x25\xc8\xc6\x4b\x39\xcf\x85\x76\x16\x74\xfa\x89\x8e\x58\xae\x8b\x13\xfb\x92\xfe\x83\x0f\x8f\xb4\xc2\x63\x57\xe1\x82\x46\x5a\x7c\x47\x3b\x69\xc6\x5b\x99\x10\x81\xc7\x26\x3b\x65\x22\x40\x5d\x06\x0a\xc2\xa3\x8e\x3a\xa5\x2a\xba\x2b\xaf\x4c\x5f\xd2\xa1\x7b\xaf\x04\x8b\xcf\x17\x41\x8b\xe9\xd6\xd3\xe7\xcf\xbf\x7f\xfc\x88\x80\x68\x56\x28\x7d\x63\x98\x9a\xaf\x68\x51\x44\xb7\x7e\xc6\xa2\xfa\x6e\xfd\x5f\x97\x1b\x53\x9b\x36\xaf\x96\xb4\x69\x8c\x0e\x0c\xa2\xf9\xcc\xda\x8a\x28\x62\xc1\x44\xd5\x64\x67\x67\x27\x34\xdd\xee\x62\x71\x4c\xc7\xbe\x24\x5a\xfe\xe7\x0a\x40\xd6\x59\x9c\x50\xb7\x5c\x8e\x33\xb9\x2e\x57\x17\xd8\xaa\x69\x88\x8d\xc1\x6d\xda\x76\x49\x04\xbc\xbb\xc6\x96\xf1\x18\x51\x77\x9f\xd4\x47\x56\xd3\xee\x9b\xbe\xcb\xe8\x46\x23\x94\xbb\xa4\x4b\x49\xba\x2d\xeb\x4b\x3a\xb7\x05\x6d\xa7\x03\x2a\x75\x5d\x7b\xb8\x8e\xfa\x4d\x3a\xa3\x8b\x8b\x70\xb1\x14\x8a\x15\x43\xed\xce\xfc\x0e\x46\xbe\xf3\xe0\xce\x7c\x56\x37\x4b\x21\x5f\xb8\x40\x0a\xda\x58\xba\x35\x97\x72\x9f\x09\x09\x23\xf2\x42\x87\x06\x7d\x82\x3e\xbe\x07\xa1\xcf\x0a\x22\x24\xd4\x1b\x61\x39\x75\x7e\x60\x6a\xad\x2f\x38\xcf\x97\xf4\x6a\x70\x45\x66\x79\xa0\x8b\xb4\xb5\x31\xd0\x1c\xf5\x54\x54\x3a\xe1\xed\xf7\x57\x0a\x36\x64\xb2\x8b\x49\x98\xcd\x1c\x2e\x8c\xf1\x9f\xa8\x37\xc1\xa8\xcLine truncated
|
||||
|
||||
func confLocaleLocale_frFrIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4419,12 +4419,12 @@ func confLocaleLocale_frFrIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_fr-FR.ini", size: 49031, mode: os.FileMode(493), modTime: time.Unix(1446027988, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_fr-FR.ini", size: 49044, mode: os.FileMode(493), modTime: time.Unix(1448150136, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_itItIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x7d\xcd\x8e\x1c\xc7\x93\xdf\x9d\x00\xdf\x21\xc5\x05\x4d\x09\x18\xb6\x20\xc9\x5f\x10\xd4\x92\x47\x43\x4a\xa4\x77\x48\xce\x9f\x43\x6a\xbd\x16\x84\x56\x76\x57\x4e\x77\x2d\xab\xab\x5a\xf5\x31\xa3\xd1\x62\x01\x3f\x80\x4f\x3e\xf9\xf8\x3f\xda\xc0\x9e\x7c\xf3\x59\x6f\xe2\x27\x71\xfc\x22\x22\xbf\xaa\xaa\x87\x94\x76\x2f\x33\x5d\x99\x91\x5f\x91\x91\x91\x91\x91\x11\x91\xf6\x70\x58\x15\xae\xdb\x2c\xdf\xd6\xa6\x73\xed\x75\xf9\x5b\xd9\x98\xef\xcb\xde\xd8\xa1\x6f\x1e\x37\xdd\xa1\xec\x6d\xdf\x98\x43\xdb\xd4\xf4\xcf\x56\xd5\xa3\xa1\x6b\xee\xdf\xbb\x7f\x6f\xd7\xec\xdd\xf2\x19\xfd\xb9\x7f\xaf\xb0\xdd\x6e\xdd\xd8\xb6\x58\x5e\xd8\xba\x76\x55\xd5\x98\xa2\x34\x1b\x2a\xd1\x36\xf4\x71\xff\x9e\xfb\xf5\x50\x35\xad\x5b\x3e\xed\xf0\xdf\x52\x61\x57\x1d\x96\xa7\x25\x35\x71\xff\x5e\x57\x6e\xeb\x55\x59\x2f\x4f\x37\x1b\x57\x94\xfa\xdd\x0c\x3d\x41\x6f\xfc\xe7\x70\x58\xbe\x76\xdb\xb2\xeb\x5b\xdb\x53\x5a\xcb\xbf\x5d\x9b\x25\xde\xb8\x75\x57\xf6\x6e\x79\x59\x52\x47\xff\xce\xad\xef\xdf\xbb\x76\x6d\x57\x36\xf5\xf2\x07\xf9\x4f\x3d\x3d\xd8\xad\xa3\x4e\x6e\xcb\x9a\x3a\xd1\xbb\xfd\xa1\xb2\x54\xe2\x8d\xfe\xb8\x7f\xaf\xb2\xf5\x76\x00\xcc\x79\x89\x1f\xf7\xef\x6d\x5a\x47\x19\xab\xda\xdd\x2c\xcf\xe8\xe7\x62\xb1\xb8\x7f\x6f\x20\x3c\xad\x08\x21\x57\x65\xe5\x56\xb6\x2e\x56\x7b\x8c\xed\x82\x13\x1a\x33\xf4\xae\xee\x9d\x71\x84\x2b\x1a\xbe\xf4\xdf\x15\x34\xc0\x95\xed\xa8\x6f\xf8\x30\x65\x6d\x6c\x07\x24\xa2\xaa\xda\x12\x22\x5f\x12\x22\xb5\x28\xa1\x6b\x6f\xcb\x6a\xf9\xf4\x31\xfe\xa1\xcf\x5d\x77\xd3\x30\x72\xe5\x07\xc6\xbf\xea\x6f\x0f\x6e\x79\xd6\xd4\x57\xae\xdd\xa3\x9f\xf6\xd0\x6f\x76\x76\x79\x26\xff\x51\x77\xeb\x0e\x0d\x21\xa4\x69\x6f\x09\x4d\xfe\xe7\xfd\x7b\x4d\xbb\xb5\x75\xf9\x1b\xa1\x8c\x30\xf3\x4a\x3e\x7e\xb3\xbf\x09\x7e\xf6\x65\xdb\x36\xed\xf2\x05\xff\xbb\x7f\x8f\x86\xbd\x42\x35\xcb\x97\x43\x73\xdd\x98\xb4\x1a\x64\xed\xcb\x6d\x0b\xfc\x21\xd7\x9a\x17\xf8\xd2\x7a\x90\x7b\xd5\xb4\xef\xb4\xe0\x77\xf4\x73\x52\x9a\x3a\xa2\x25\x9b\x71\x2f\x6c\x4d\x73\xc0\x00\xdf\xbb\xae\x2f\x89\x0e\x4c\xe5\x72\x30\x9a\x70\x5b\xec\x09\xab\x07\x4b\x14\x97\x11\x9e\xdd\x53\x3a\x93\x85\xd6\x67\x37\x9b\x66\xa8\xfb\x55\xe7\xfa\x9e\xe6\xb5\x5b\x3e\xdf\x53\x57\x7a\xa9\xc7\x14\x54\xee\x91\x82\xd0\x74\xcd\xc1\xdc\xbf\x77\xdb\x0c\x61\xce\xfd\x54\x6b\xea\x91\x12\x3c\xc8\x6e\x75\xe5\x5c\x41\xb3\xdb\xd3\xba\x02\xfd\x0d\x55\x45\x18\xfd\x65\xa0\x61\x75\xcb\x0b\xfa\x22\xb4\xc8\xd7\xfd\x7b\x65\xd7\xd1\x2f\xd4\xbe\xae\xdc\x9e\xab\xd8\xd8\x7a\x43\xa3\x3b\xad\x6b\x02\xe5\x59\xfd\xb1\x73\xb6\xdd\xec\x7e\x42\x4f\xf1\x63\xf9\xba\xdc\xb8\x76\x23\x94\x79\x64\xca\x41\x65\xcb\xb7\x4a\x5c\xdc\x8a\x6f\x04\x94\xd3\x14\x20\xa4\x82\xaa\xe1\xfa\xcb\x9a\xc6\x50\x55\xd4\x80\xfe\x5a\x3e\x97\xff\x1e\x9b\x7d\xd9\x03\x07\x44\x8c\x84\xbb\x47\x65\x9a\x69\x0e\xae\x35\x65\x45\xdc\xa2\xdc\x13\xb3\xb8\xbe\x2e\x09\x49\x45\xb3\x79\x47\x0b\x06\x4b\x9e\xba\x71\xe9\x0c\x15\x28\x89\xd6\xcb\x0a\x73\x59\x17\xc4\x6e\x9a\x6d\x67\xba\xc1\x3c\x61\xc8\x13\xae\xe5\xca\x5e\xd3\xaa\xa2\x69\xdf\x6e\x79\xf2\xbf\xb2\xa6\xb7\xed\xd6\xf5\xcb\x07\xab\x35\x2d\xd3\x77\x0f\xcc\xae\x75\x57\xcb\x07\x0f\xbb\x07\x5f\xd3\x6a\x75\xce\x6c\x87\xb2\xb0\x5f\x7d\x6a\xbf\x06\xeb\x31\xb6\xa7\x01\x6b\xaf\xa8\x3b\x96\x59\x92\xdd\xaf\x4b\x4b\xd5\xfe\x32\xd8\x6a\xd3\x74\x16\xad\x32\xfa\xad\x39\x30\x57\xf8\x08\x48\xfc\x65\x20\x3e\xb2\x2a\xd6\xc2\x18\xb9\x77\xb5\xdb\x38\x1a\x30\xc1\xbd\xb8\xbd\xfc\xcb\xf9\x89\xb9\xa0\xa9\xde\xb6\x8e\x7f\xd3\x1f\x2a\xf0\x85\x69\xcc\x9b\xf2\xc9\xb7\x34\x0f\x54\x54\xb0\x94\x11\xda\x13\xdb\xdb\xb5\xed\x9c\xe4\x63\xfd\xbe\x29\x0f\x4c\xb1\x45\xc8\xd9\x11\x38\x71\xd5\xae\x1f\xcd\xda\x0c\x13\xa0\x4a\x22\xeb\x20\x22\x4e\x6a\xa1\x2c\x45\xf7\x5b\x45\x33\x66\x65\xdf\xf4\x40\xe9\xf3\x97\x2f\x5f\x3d\xf9\x96\x71\x44\x73\x5f\x5e\x95\x1b\x4b\xb3\x71\xf5\x1f\x57\x5b\x57\xbb\xd6\x56\x2b\x5a\x6d\x98\x01\x1e\x28\x0d\xa6\xeb\x2a\xe2\x70\x44\x24\x2f\x9a\xc2\x56\x65\xff\xfb\x5f\xcd\xe5\xe5\x39\xba\xd4\xef\x96\x17\x44\x7b\x4d\x8b\x1d\xa1\xfb\xa5\x02\xd6\xb4\xdd\x37\x3b\x67\xb0\x56\x0c\xa0\x4c\x73\x15\x71\xd4\x32\x92\x42\x67\xa9\x01\xd7\xb6\x2b\xe2\xc0\xfd\x2d\x50\xce\xb5\x1e\x03\x96\xda\x68\x51\xd4\x4d\x6f\xd6\xc4\x64\x51\x4a\x6b\x28\xeb\x6b\xea\x5d\x41\x88\xf7\x88\xc9\x8b\x22\xc9\x14\x8d\xa3\xb9\xa4\xc2\x44\xb2\xcd\x8d\x21\x4e\xd9\xda\x0d\x6d\x24\x9d\x79\xb0\x78\x60\x88\x18\xcd\x83\xc7\x0f\xa8\xc2\xba\x59\x09\x77\x01\x7b\x2f\xca\xce\xd2\x62\x59\xb5\x61\xb3\x21\xce\xf9\xf7\xcd\xe0\x3b\xa2\xf9\x26\xcd\x37\x37\x65\xbf\xa3\x4d\xcc\xf0\x06\x42\xec\x81\x2a\x37\x5c\xa5\x51\x56\x93\x0d\xdc\xb3\x32\x9d\xe4\x53\x06\xf4\x9f\x33\x03\xbe\x7f\xcf\x4f\xd6\x0c\x9d\x11\x41\x7d\x8b\x11\x33\x63\x3b\x3d\x1c\x2a\x9a\x61\xcf\x09\x69\xb7\x8f\x44\x33\x9f\x17\x56\xea\xa6\x2d\xaf\x4b\x5a\x1e\x25\x88\xa7\x56\x2a\xab\x68\x1d\x0e\x63\x96\x6d\x88\xd7\xf3\x9e\xb7\xd9\xd1\xf2\x6a\x3e\x12\x4e\xb4\xca\x28\xc4\xbc\x6e\x80\x2b\x57\x65\x1b\x41\x80\x0b\x84\x33\x10\x37\x35\xa5\x89\xac\x8c\xa5\x91\xd6\x11\xf5\x96\xa6\xa3\xf9\x22\x5c\xd0\xff\xea\xda\x02\xae\xf6\xcb\xb7\x28\x5b\xb7\x01\x38\xd8\xe0\x40\x12\x03\x56\xcf\xdLine truncated
|
||||
var _confLocaleLocale_itItIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x7d\x4b\x8f\x1c\xc7\x93\xdf\xbd\x3f\x45\x8a\x7f\x10\x94\x80\x61\x0b\xfa\x6b\xfd\x80\xa0\x96\x3c\x22\x29\x91\x5e\x3e\x66\x39\xa4\xfe\x5e\x0b\x44\xab\xba\x2b\xa7\x3b\x97\xd5\x55\xad\x7a\xcc\x68\xb4\x58\xc0\x1f\xc0\x27\x9f\x7c\xdc\xa3\x0d\xec\xc9\x37\x9f\xf5\x4d\xfc\x49\x1c\xbf\x88\xc8\x57\x55\xf5\x90\xfa\xef\xf2\xc0\xe9\xca\x8c\x7c\x45\x46\x46\x46\x44\x46\x46\x16\xc7\xe3\xba\xb4\xdd\x76\xf5\xb6\x36\x9d\x6d\xaf\xdd\x6f\xae\x31\x3f\xb8\xde\x14\x43\xdf\x3c\x6c\xba\xa3\xeb\x8b\xbe\x31\xc7\xb6\xa9\xe9\x4f\x51\x55\x0f\x86\xae\x59\x2c\xf6\xcd\xc1\xae\x9e\xd2\x7f\x8b\xb2\xe8\xf6\x9b\xa6\x68\xcb\xd5\x45\x51\xd7\xb6\xaa\x1a\x53\x3a\xb3\x25\xf0\xb6\xa1\x8f\x85\xfd\xf5\x58\x35\xad\x5d\x3d\xe9\xf0\xb7\x58\xec\x6d\x75\x5c\x9d\x3b\xaa\x7d\xd1\xb9\x5d\xbd\x76\xf5\xea\x7c\xbb\xb5\xa5\x93\xcf\x66\xe8\x09\x74\xab\x5f\xc3\x71\xf5\xda\xee\x5c\xd7\xb7\x45\xef\x16\x2d\xff\xb4\x6d\x9a\x76\x63\x37\x9d\xeb\xed\xea\xd2\x51\xf7\xfe\x62\x37\x8b\x6b\xdb\x76\xae\xa9\x57\x3f\xca\x5f\xbb\x38\x16\x3b\x4b\x7d\xdb\xb9\xba\x58\xf4\xf6\x70\xac\x0a\x02\x7f\xa3\x3f\x16\x55\x51\xef\x06\x40\x3c\x77\xf8\xb1\xd8\xb6\x96\x92\xd7\xb5\xbd\x59\x3d\xa2\x9f\xcb\xe5\x72\x31\x10\x5e\xd6\x84\x80\x2b\x57\xd9\x75\x51\x97\xeb\x03\xc6\x73\xc1\x09\x8d\x19\x7a\x5b\xf7\xd6\x58\xc2\x0d\x8d\x98\xbb\x6d\x4b\x1a\xd5\xba\xe8\xa8\x53\xf8\x30\xae\x36\x45\xb7\xe0\x7a\xea\x82\xf0\xf6\x92\xf0\xa6\xe5\x16\xf6\x50\xb8\x6a\xf5\xe4\x21\xfe\x50\x57\xbb\xee\xa6\x61\x54\xca\x0f\x1a\xf2\xba\xbf\x3d\xda\xd5\xa3\xa6\xbe\xb2\xed\x81\xfa\x57\x1c\xfb\xed\xbe\x58\x3d\x92\xbf\x0b\x82\x38\x36\x84\x81\xa6\xbd\x25\xb4\xf8\x9f\x8b\xa6\xdd\x15\xb5\xfb\x8d\x30\x44\xa8\x78\x25\x1f\xbf\x15\xbf\x31\x42\x0e\xae\x6d\x9b\x76\xf5\x82\xff\x2c\x68\xa4\x6b\xd4\xb1\x7a\x39\x34\xd7\x8d\x49\xea\x40\xce\xc1\xed\x5a\xe0\x0b\x99\x85\x79\x81\x2f\xa9\x04\x99\x57\x4d\xfb\x5e\x8b\x7d\x4f\x3f\xc7\x65\xa9\x0f\x5a\xae\x19\x75\xa0\xa8\x09\xe3\x9c\xff\x83\xed\x7a\x47\xd3\x6d\x2a\x9b\x43\xb9\x45\x51\x1e\x08\x8b\xc7\x82\x68\x2a\x23\xad\xe2\x40\xe9\x3c\xfd\x52\x5b\xb1\xdd\x36\x43\xdd\xaf\x3b\xdb\xf7\x34\x87\xdd\xea\xd9\x81\xba\xd1\x4b\x2d\xa6\xa4\x62\x0f\x14\x64\x31\x0b\xb2\xb8\x6d\x86\x30\xbf\x7e\x5a\x25\x71\x1e\x1e\x83\xeb\xd6\x57\xd6\x96\x34\x93\x3d\x2d\x18\xa2\xb1\xa1\xaa\x08\x8b\xbf\x0c\x34\x9c\x6e\x75\x41\x5f\x84\x0c\xf9\x5a\xb8\xae\xa3\x1f\xa8\x78\x53\xd9\x03\x15\xdf\x16\xf5\x96\xc6\x74\x5e\xd7\x04\x47\x33\xf8\x53\x67\x8b\x76\xbb\x7f\xb7\x90\xbf\xab\xd7\x6e\x6b\xdb\x2d\x93\xde\xfc\xe4\x82\x90\x56\x6f\x85\x7e\xb8\x76\x5f\x39\x91\x47\x53\x82\x58\x4a\xaa\x82\x2a\x76\x35\x75\xba\xaa\xde\x2d\xf4\xc7\xea\x99\xfc\x55\xcc\xf5\xae\xc7\x88\x89\xd6\x08\x4f\x0f\x5c\x9a\x67\x8e\xb6\x35\xae\xa2\x75\xef\x0e\xb4\xec\xaf\xaf\x5d\xb3\x28\x9b\xed\x7b\x5a\x09\x58\xc0\xd4\xfe\xa5\x35\x04\xef\x88\x8e\x5d\x85\x49\xab\x4b\xe2\x1b\xcd\xae\x33\xdd\x60\x1e\x33\xe4\x19\x57\x72\x55\x5c\xd3\x72\xa1\xf9\xdd\xed\x78\x96\xbf\x2e\x4c\x5f\xb4\x3b\xdb\xaf\xee\xad\x37\xb4\xfa\xde\xdf\x33\xfb\xd6\x5e\xad\xee\xdd\xef\xee\x7d\x43\x8b\xd0\x5a\xb3\x1b\x5c\x59\x7c\xfd\x79\xf1\x0d\xd8\x88\x29\x7a\x1a\xa9\x76\x8a\x7a\x53\x30\x7b\x29\x0e\x1b\x57\x50\xb5\xbf\x0c\x45\xb5\x6d\xba\x02\xad\x32\xbe\x0b\x73\xe4\xa5\xfe\xc9\x02\xd3\x41\x9c\x61\x5d\x6e\x84\xc1\x71\xe7\x6a\xbb\xb5\x34\x5c\x02\x7b\x71\x7b\xf9\x77\xcf\xcf\xcc\x05\xcd\xeb\xae\xb5\xfc\x9b\xfe\xa3\x02\x5f\x9a\xc6\xbc\x71\x8f\xbf\x5b\x2e\xa8\xa4\xa0\x28\xa3\xa8\xc7\x45\x5f\x6c\x8a\xce\x72\x36\xd6\xe6\x1b\x77\x64\xc2\x2c\x7d\xc6\x9e\x80\x89\x39\x76\x7d\x36\x53\xd3\xc5\x4d\x15\x44\x7e\x40\x84\x1a\x6b\xa0\x1c\x45\xf3\x5b\x45\x2f\x26\xe3\xd0\xf4\x40\xe5\xb3\x97\x2f\x5f\x3d\xfe\x8e\x71\x43\xd3\xed\xae\xdc\xb6\xa0\x59\xb8\xfa\x8f\xeb\x9d\xad\x6d\x5b\x54\x6b\x5a\x4e\xc0\x3c\x8f\x70\xb9\xe8\xba\x8a\x38\x16\x91\xc5\x8b\xa6\x2c\x2a\xd7\xff\xfe\xcf\xe6\xf2\xf2\x39\x75\xa7\xdf\xaf\x2e\x88\xd0\x9a\x96\x18\x7a\xf7\x4b\x05\x5c\x69\xa3\x6f\xf6\xd6\x60\x35\x18\x00\x99\xe6\x2a\x62\xa6\x65\xd4\x84\x8e\x2e\x17\xb6\x6d\xd7\xc4\x4b\xfb\x5b\xe0\x99\xeb\x3c\x05\x2b\x95\x11\xf1\xd7\x4d\x6f\x36\xc4\x31\x51\x4a\x2a\x70\xf5\x35\xf5\xac\x24\x6c\x7b\x94\xe4\x25\x91\x64\xca\xc6\xd2\xfc\x51\x59\x22\xd2\xe6\xc6\x10\xf3\x6b\x8b\x2d\xed\x06\x9d\xb9\xb7\xbc\x67\x88\xfe\xcc\xbd\x87\xf7\x96\x8b\xba\x59\x0b\xe7\x00\xa7\x2e\x5d\x57\xd0\xc2\x58\xb7\x61\xc3\x20\x76\xf8\x92\x10\x77\x1c\x1a\x9a\x30\xe4\x12\x7a\x7b\x50\x52\x55\x18\x0f\x25\xc4\xd6\x11\xd5\x15\x44\xfa\x84\x49\xde\x14\x88\xd5\xd7\x09\xf3\xc1\x54\xa4\xc3\xf7\xec\x4a\x27\xf9\x39\x08\x51\x7e\x2b\x0f\xca\x4a\xd2\x40\xb8\x13\x0f\x0c\x91\xa3\xa5\xef\xeb\xa1\xe9\x8b\xe5\x62\xe1\xe7\x70\x86\xf0\x88\xc4\xbe\x03\x36\xb8\xba\xf3\xe3\xb1\xa2\x89\x57\x16\x48\x9b\x78\xa4\xa4\xd9\xac\xb0\x6c\xb7\xad\xbb\x76\xb4\x56\x1c\x28\xaa\x56\xca\xa3\xc1\xf7\xc3\x98\x4f\x1b\xe2\xef\xbc\xb3\x6d\xf7\xb4\xd6\x9a\x4f\x98\x1b\xad\x33\xba\x31\xaf\x1b\x9a\x10\x90\x6e\xc2\x9e\x02\x58\x20\xa7\x81\xd8\xa8\x71\x26\x72\x33\x96\x30\x5a\x4b\x04\xed\x4c\x47\xf3\x48\x34\x41\x7f\xab\xeb\x02\x70\xb5\x5f\xc9\xa5\x6b\xed\x16\xe0\xc4\x08\x07\x12\x06\xb0\x9Line truncated
|
||||
|
||||
func confLocaleLocale_itItIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4439,12 +4439,12 @@ func confLocaleLocale_itItIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_it-IT.ini", size: 46472, mode: os.FileMode(493), modTime: time.Unix(1446027988, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_it-IT.ini", size: 46492, mode: os.FileMode(493), modTime: time.Unix(1448150140, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_jaJpIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\xbd\x7b\x6f\x5c\xc5\xb2\x38\xfa\x7f\xa4\x7c\x87\xb5\x73\x94\x0b\x48\x89\x23\xe0\x5e\xe9\x0a\x31\x9c\x0b\x09\x1b\xb8\x3f\x12\x72\x70\x38\xe8\x27\x84\x86\xf1\xcc\xb2\x3d\x27\xe3\x59\xc3\xac\x99\x18\xef\xa3\x23\x79\xec\x3c\x9c\xd8\xd9\x0e\x84\xc4\x79\x91\x07\x24\x71\x62\x27\x76\x42\x60\x13\x62\x93\x7c\x97\x33\x9e\xb1\xfd\x17\x5f\xe1\x57\xaf\x7e\xae\xb5\xc6\x66\x1f\x24\x44\x3c\xab\xbb\xab\xbb\xab\xab\xab\xab\xaa\xab\xab\x0a\xb5\x5a\xbe\x14\xc6\xc5\xdc\x7b\xd1\xc6\xbd\xf1\x8d\x85\xeb\xed\xd6\x7c\x67\xe9\xc6\xc6\x0f\x27\xda\xad\xb9\x76\xeb\x46\x7b\x62\xa5\x3d\xb9\xd8\x9e\xbc\xd0\x9e\xbc\xda\x9e\xf8\xb5\x3d\x39\xf5\x5e\xb9\xd1\x9e\xf8\xb9\x3d\xb9\xda\x9e\x3c\x0f\x5f\x76\xee\xd8\xb9\x63\x38\x1a\x09\x73\x58\x01\x3f\xde\xdc\xb9\xa3\x54\x88\x87\x07\xa2\x42\xbd\x04\x1f\xc7\xdb\x93\x93\xed\x89\x5f\xda\x93\x77\xda\x93\xd7\xa8\xc2\xe9\x9d\x3b\xc2\xaf\x6a\x95\xa8\x0e\x6d\x26\xee\x21\xd0\x89\xe5\xf6\xe4\x5c\x7b\xf2\x21\x15\xdf\x07\x78\x61\xa5\x06\x4d\x2f\x51\xcf\x73\x3b\x77\xc4\xe5\xa1\x6a\xbe\x5c\xcd\x61\xbf\x13\xb7\xdb\x93\x4f\xf8\xff\x52\x10\x35\x1b\x76\xc9\xf7\xed\x89\xbb\x30\x4c\x29\x6c\xd6\xdc\x32\x18\x0c\x42\xac\x87\x43\xe5\xb8\x11\xd6\x73\xeb\x97\x57\x36\x67\x7e\xdc\xb9\x63\x34\x1c\x88\xcb\x8d\x30\xf7\xe9\xbb\xef\xa8\xfa\x00\xe2\x58\x58\x8f\xcb\x11\x74\x3c\x79\x0e\xc7\x36\xf1\xb4\x3d\x39\x4f\x1d\xd7\x0a\x43\x38\xe3\x2b\xfc\x75\xe7\x8e\x46\x38\x52\xab\x14\x1a\xf8\xed\x24\xf6\x84\xd3\x79\x40\xd3\x01\x28\x95\x42\x75\xa8\x89\x0d\x18\xc3\x3b\x77\x14\xeb\x21\xd4\xcd\x57\xc3\xd1\xdc\xda\x6f\xd7\xba\x53\xe7\xfa\xfa\xfa\x76\xee\x68\xc6\x61\x3d\x5f\xab\x47\x83\xe5\x4a\x98\x2f\x54\x4b\xf9\x11\x42\xd1\xe4\x5d\xea\xe4\x1f\x04\x8d\xb1\x74\xa1\x3d\x71\x8b\x86\xb8\xd8\x6e\x2d\xb4\x5b\xf7\x79\xae\x61\x09\x70\x94\x2f\xc4\x3e\x9a\xba\x4f\xa7\xda\xad\x17\xb8\x4e\xd8\x43\xb5\x30\x62\x01\xed\x9c\x3b\x0b\xab\x31\x52\x28\x57\x72\xef\xee\xc5\x7f\x70\x6a\x71\x3c\x1a\xd1\xda\x7d\x4d\x2b\xbe\xac\x56\xad\x1e\xe6\x1b\x63\xb5\x30\xd7\x39\x79\xb6\x73\xe2\x4e\xe7\xcc\x55\x98\x49\xa1\xd6\x28\x0e\x17\xa0\x4b\x18\xd6\x0f\x34\xbe\x16\xfc\x81\x9d\xd5\xc3\x5a\x04\x28\x8d\xea\x63\x00\x69\xa1\x3d\xf9\x1d\xa1\x6f\x0a\xfe\xde\xb9\x23\xaa\x0f\x15\xaa\xe5\xbf\x15\x1a\x88\xdc\xf5\x9f\x8f\xaf\xff\xfa\xed\xce\x1d\x23\xe5\x7a\x3d\xaa\x43\xe5\x1b\x40\x04\xd0\xe7\xce\x1d\x80\xa0\x3c\x82\xc9\x75\x2f\x3e\x22\x72\x3c\x9e\x80\x84\x55\x46\xca\x43\x75\xc4\xbd\xae\xb5\x3e\xbf\xb2\x71\x6b\x86\x0b\x07\xa3\xfa\x51\xbb\x3d\xe0\xee\x3e\xcd\x7d\xb9\xdd\x5a\x4a\x05\x07\x83\xb3\x40\xa9\xc1\x15\xaa\xb0\x82\x54\xc6\x9f\xda\x13\xdf\xac\x2f\xdd\x5a\x3f\x77\x72\xe7\x8e\x42\x69\x04\x10\x5f\x2b\x54\xc3\x4a\x8e\xbf\x6d\x8c\x9f\x40\xec\x4d\x9e\x85\x25\x82\xf2\x62\x31\x6a\x56\x1b\xf9\x38\x6c\x34\xca\xd5\x21\x5c\x20\x20\xd2\x45\xa2\x53\x20\x95\xa9\x8d\x7b\x0f\x3b\x4b\x57\x60\x11\x55\xb9\xfa\x30\x16\x35\x35\x45\xe4\xda\xad\x09\x5a\xed\x1b\x34\x6e\x9f\x10\xa4\xb2\xe9\xc2\xaa\xad\xc0\xd1\xf4\xe2\xfc\x60\x18\xe2\xe2\x4e\xd3\x96\x5c\xc5\x25\x46\x80\x00\xea\x07\xb5\xd0\xb5\x66\xa5\x02\x88\xff\xb2\x19\xc6\x0d\x00\x85\x9d\x2d\x22\xaa\x00\x67\xbc\x61\x91\xaa\xcb\x71\x0c\xe5\xb9\x8d\x85\x1f\x37\x11\xd7\x48\x0b\xd5\x22\x60\x40\x91\xc2\x13\x66\x1d\x58\xf2\x59\x1c\x16\xea\xc5\xe1\xcf\x71\x8a\xf8\x47\xae\x7b\xfb\xda\xfa\x4f\xdf\x13\xd5\xf7\x22\x13\xa4\x57\x43\xab\xd2\xa5\xee\xb1\x18\x95\x90\x83\x3c\x51\xa3\x86\x7e\xca\xd5\xb8\x51\xa8\x54\xa0\x23\xf9\x2b\xa7\x36\xc2\xaf\x04\x73\x95\x06\xd4\x28\x37\x00\x9f\x9d\xa9\xef\x3a\x57\xaf\x23\xaf\xbb\x35\x83\x18\x48\x54\xec\x9e\x9e\xde\xbc\x09\xcb\x5b\x8a\x8a\x47\x61\x67\x22\x53\x82\xe1\x7c\x30\x18\x00\xa6\x5f\xaa\x87\x41\xbd\x59\xad\x02\xae\x83\xf7\xa2\xa1\x38\x80\xfe\xca\xa5\x30\x38\x40\x75\xf7\x04\xb5\x4a\x58\x88\xa1\x4a\x58\x28\x05\x6f\x16\x82\x46\xa1\x3e\x14\x36\x72\xbb\xf2\x03\xc0\x0b\x8e\xee\x0a\x86\xeb\xe1\x60\x6e\xd7\xee\x78\xd7\x5b\xef\x35\xa1\x59\xa5\x5c\x0d\xe3\x37\xf7\x15\xde\x0a\x8a\x05\x28\x01\xec\x8f\x05\x03\x21\x50\x6e\x88\x7d\x05\xb0\xc1\xaa\x43\x61\x50\xa8\x8e\x35\x86\xb1\xc3\x72\x35\x80\x3f\xe2\x00\x99\xd0\x5f\x10\x83\x5f\x36\x81\x75\xe5\x4b\x03\x8a\x93\xc3\x78\xe8\x63\x3d\x8c\x83\x83\x63\xfd\xff\xf6\xe1\x9e\xe0\x70\x14\x37\x86\xea\x21\xfd\x0d\xff\x83\xfa\xaf\x07\x51\x3d\x38\x52\x3e\xf0\x0e\x2c\x02\x34\x65\xa4\xb4\x27\x4f\x11\xb2\x5f\xb4\x27\x2f\x33\x71\x28\xe2\xc1\x2a\xb8\xf9\x93\x35\x00\x79\xeb\xf7\x96\x36\x6f\x5d\xc7\x83\x20\x6e\xe4\xf4\x49\x91\x5c\xc0\x6c\xde\x02\xe0\x85\x33\xf9\xe0\x89\x45\x41\xb1\xe0\xff\xe0\x58\xfc\x65\x25\xf8\xe0\xd0\xa1\x8f\x0e\xbc\x13\x20\x39\xe2\xa2\x01\xc5\x3c\x09\x9a\x8d\xc1\xff\x37\x3f\x14\x56\xc3\x7a\xa1\x92\x2f\x96\x03\x18\x57\xf7\xe2\xa9\xce\xc3\x39\x22\xc4\x49\x5c\xd7\x89\x6f\xd6\x7e\x7b\xb1\xfe\xed\x3d\xda\xdb\x77\xdb\xad\xd9\x76\xeb\x66\xbb\x75\x01\x19\xc3\xf8\x04\x50\x67\x5c\x01\xa6\x0b\x54\xd5\xdf\xff\x61\xd0\x9e\xfc\x5e\xef\x87\x42\x63\x58\x86\x0c\x95\xbe\xac\x20\xb6\x65\x38\x47\x86\xc3\x00\xb7\x67\x80\x75\x82\x68\xd0\x47\x6e\x50\x2a\x34\x0a\x03\x40\x0b\x80\xe5\xb0\x5e\xcf\xc3\x39\xd1\x18\xc3\xa5\x22\x98\x59\x95\x19\x1a\x6Line truncated
|
||||
var _confLocaleLocale_jaJpIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x7d\x6b\x6f\x1c\x55\xb6\xe8\xf7\xfe\x15\x35\x39\x8a\x00\x09\x8c\x80\x73\xa5\xab\x11\xcd\xb9\x4c\x60\x98\xb9\xe2\x75\x09\x68\x74\x85\xa2\xa6\xdd\x5d\xb6\xfb\xa4\xdd\xd5\xd3\x55\x1d\xe3\x39\x3a\x92\xdb\x4e\x82\x13\x3b\xe3\x90\xe0\x98\x3c\xc0\x04\x92\xd8\xb1\x89\x9d\x10\x1e\x49\x6c\x12\xe9\xfe\x94\xd3\xee\xb6\xfd\x89\xbf\x70\xd7\x6b\x3f\xab\xca\x76\xe6\xcc\x68\x44\xda\xb5\xdf\x6b\xaf\xbd\xf6\x5a\x6b\xaf\x47\xb9\xd9\x2c\x55\xc3\xb8\x52\x7c\x2b\xda\x59\x9e\xd8\x59\xf9\xba\xdb\x59\xea\xad\x2d\xee\x7c\x77\xaa\xdb\x59\xe8\x76\x16\xbb\x93\x1b\xdd\xa9\xd5\xee\xd4\x7c\x77\xea\x6a\x77\xf2\x61\x77\x6a\xfa\xad\x5a\xd2\x9d\xfc\xa9\x3b\xb5\xd9\x9d\xba\x08\x5f\x0a\x85\x91\x68\x34\x2c\x62\x31\x7e\xfa\xa6\x50\x2d\xc7\x23\x83\x51\xb9\x55\x85\x6f\x13\xdd\xa9\xa9\xee\xe4\x2f\xdd\xa9\x9b\xdd\xa9\x6b\x54\x7e\xa6\x10\x7e\xda\xac\x47\x2d\x68\x31\xb9\x8c\x1d\x4e\xae\x77\xa7\x16\xba\x53\x77\xa8\xf4\x76\x61\x24\xac\x37\xa1\xe1\x97\x34\xe8\x42\x21\xae\x0d\x37\x4a\xb5\x46\x11\x47\x9c\xbc\xd1\x9d\xba\xcf\xff\xe5\xef\x51\x3b\xb1\x0b\xbe\xed\x4e\xde\x82\xf9\x71\x59\xbb\xe9\x16\xc1\x3c\xa0\xbb\x56\x38\x5c\x8b\x93\xb0\x55\xdc\xbe\xbc\xb1\x3b\xfb\x43\x61\x2c\x1c\x8c\x6b\x49\x58\xfc\xcb\x9b\x7f\x50\xb5\xa7\x0b\x27\xc2\x56\x5c\x8b\x60\xcc\xa9\xf3\x38\xa9\xc9\x07\xdd\xa9\x25\x1c\xb3\x59\x1e\xc6\x75\x5e\xe1\x8f\x85\x24\x1c\x6d\xd6\xcb\x09\x7e\x3a\x8d\x83\xe0\x2a\xbe\xa7\x55\x4c\x17\xea\xe5\xc6\x70\x1b\xab\x33\x4c\x0b\x95\x56\x08\x35\x4b\x8d\x70\xac\xb8\xf5\xeb\xb5\xfe\xf4\xf9\x81\x81\x81\x42\x3b\x0e\x5b\xa5\x66\x2b\x1a\xaa\xd5\xc3\x52\xb9\x51\x2d\x8d\x12\x58\xa6\x6e\xd1\x00\x3f\x53\x57\x0c\x99\xf9\xee\xe4\x75\x9a\xdc\x6a\xb7\xb3\xd2\xed\xdc\xa6\x25\x86\x55\x00\x4c\xa9\x1c\xfb\xb0\xe9\x3f\x98\xee\x76\x9e\x14\xa8\xfb\x46\x79\xd4\xea\xb1\x77\xfe\x5c\x21\x1c\x2d\xd7\xea\xc5\x37\x5f\xc0\x7f\x60\x45\x71\x3c\x16\xd1\x4e\x7d\x4e\x9b\xbb\x2e\x7b\xd4\x0a\x4b\xc9\x78\x33\x2c\xf6\x4e\x9f\xeb\x9d\xba\xd9\x3b\x7b\xb5\x50\x29\x37\x93\xca\x48\x19\x06\x83\xf9\x7c\x47\x13\xeb\xc0\x8f\x02\x54\x6d\x46\x00\xc3\xa8\x35\x0e\xbd\xac\x74\xa7\xbe\x22\x80\x4d\xc3\xef\x42\xd4\x1a\x2e\x37\x6a\x7f\x2b\x27\x08\xcd\xed\x9f\x4e\x6e\x3f\xfc\xa2\x30\x5a\x6b\xb5\xa2\x16\x54\x5d\x84\xdd\x86\xd1\x0a\x00\x93\x12\xf6\x51\xec\x5f\xba\x4b\x28\x77\xd2\xef\x06\x6b\x8c\xd6\x86\x5b\x08\x6b\x5d\x69\x7b\x69\x63\xe7\xfa\x2c\x95\x0d\x45\xad\xe3\x76\x6b\x80\xd6\x6d\x5a\xf0\x7a\xb7\xb3\x96\xd5\x19\xcc\xcb\xea\x48\xe6\x55\x6e\xc0\x7e\x51\x11\x7f\xe9\x4e\x5e\xd8\x5e\xbb\xbe\x7d\xfe\x74\xa1\x5c\x1d\x05\x40\x37\xcb\x8d\xb0\x5e\xe4\x4f\x3b\x13\xa7\x10\x62\x53\xe7\x60\x47\x0a\xe5\x4a\x25\x6a\x37\x92\x52\x1c\x26\x49\xad\x31\x8c\xfb\x01\x98\xb8\x4a\xc8\x08\x58\x31\xbd\xb3\x7c\xa7\xb7\x76\xa5\xa0\x8b\xe5\xef\xf1\xa8\xad\x77\xbf\xd8\xed\x4c\xd2\xce\x2e\xd2\x8c\xfd\x4d\xe7\xba\xa6\x7f\xab\xb2\x74\x86\xcb\x8a\x4b\x43\x61\x88\x5b\x39\x43\xc7\x6d\x13\x37\x14\x7b\x83\x7e\xbe\x93\x6d\x6d\xb6\xeb\x75\x00\xf6\x5f\xdb\x61\x9c\x40\x3f\x38\xd0\x2a\x02\x08\x20\xc5\x87\x11\x50\xb7\x16\xc7\x50\x5c\xdc\x59\xf9\x61\x17\xe0\x0b\xfb\xde\xa8\xc0\xba\xd5\xb6\xdf\x67\x7a\x50\x28\x7c\x1c\x87\xe5\x56\x65\xe4\x58\x81\xff\x2d\xf6\x6f\x5c\xdb\xfe\xf1\x5b\x44\xeb\x3d\x10\x02\x51\xd2\xa0\x23\x0f\xa5\x46\xaa\x44\x55\xa4\x09\xf7\x65\xaa\x85\x8f\x6b\x8d\x38\x29\xd7\xeb\xc7\x0a\xf2\xa3\xa8\x90\xfc\x21\xf5\xb6\x89\xd3\x48\x6a\x09\x40\xaf\x37\xfd\x55\xef\xea\xd7\x48\xb5\xae\xcf\xe2\x92\x53\xf5\xfa\x67\x66\x76\xbf\x39\x5d\xa8\x46\x95\xe3\x70\xe4\x90\xc4\xc0\x3c\xde\xa0\xbf\x80\xdc\xbd\x15\x0d\xc7\xb8\xdb\xcb\x9b\xbd\x99\xf9\x6e\xe7\x72\x77\x72\xa6\xf7\xcd\x8f\xbd\xf3\xd3\xdd\x89\x4e\xb7\x73\x91\x80\x28\x87\xbe\xdb\x59\xed\xdd\x38\xd3\xbf\xfa\x23\xd4\xef\x9d\xfd\xa6\xdb\x99\x86\xca\xdd\xce\x63\xfc\xd1\x59\x85\xfa\xc1\xab\xe5\x20\x29\xb7\x86\xc3\xa4\x78\xa8\x34\x08\x64\xe0\xf8\xa1\x60\xa4\x15\x0e\x15\x0f\x1d\x8e\x0f\xbd\xf6\x56\xbb\x56\x0d\xeb\xb5\x46\x18\xbf\xfa\x62\xf9\x35\xe8\xa3\x3b\x09\xed\xe6\x76\x56\xee\x74\x27\x61\x9c\x25\xf8\xdd\xed\x40\xaf\x30\x8b\x93\xbf\x6d\x76\x0a\xb8\x55\x40\x9e\x4a\xd5\x41\x45\xa3\x87\xe3\xa0\xdb\x59\x87\x81\xde\x19\x3f\xfa\x7f\xde\x86\x7f\xdf\x8f\xe2\x64\xb8\x15\xf2\x1f\xf0\x5f\xa8\xfe\x4a\x40\x33\x02\xf4\x58\x0f\x3e\xac\xbd\xf1\x07\xf8\x73\xb6\xf7\xe4\xd4\xce\xad\x0e\x0d\x71\xb9\x3b\x31\x59\x80\x1e\x19\x76\xdd\xa9\xcf\x68\x69\x4f\xba\x53\x97\x19\x69\x04\xa5\xb0\x06\x92\x80\x74\x05\x00\xc8\xf6\xf2\xda\xee\xf5\xaf\x81\xf2\xc7\x49\x51\x5f\x0c\xfe\xf6\xe6\x52\x17\xe8\x5a\x08\x93\xdf\x35\x52\x28\x28\x95\x1d\x7a\x67\x3c\xfe\x6b\x3d\xf8\xf3\xbb\xef\xbe\x87\x8b\x00\x0c\xc5\x5d\x05\x5c\xba\x1f\xb4\x93\xa1\xff\x59\x1a\x0e\x1b\x61\xab\x5c\x2f\x55\x6a\xb0\xc2\xb5\xfe\xa5\xcf\x7a\x77\x16\x08\x3d\xa7\x70\xe3\x27\x2f\x6c\xfd\xfa\x64\xfb\x8b\x65\x3a\xe5\xb7\x6c\xc8\xe2\xfa\xe3\xb8\x0e\xe4\x16\x30\xee\xe8\xd1\xb7\x83\xee\xd4\xb7\xea\x80\x94\x93\x11\x99\x6e\x01\xc6\x46\xd8\xcb\x5c\x72\x41\xbb\x96\x09\x20\xfb\xf8\x06\xb2\x7e\x18\x36\x6c\xb5\x4a\x70\x6d\x24\xe3\xb8\xa5\x34\x58\x5e\xbf\xd9\x9d\x52\x3f\x80\xaa\xb7\x1f\x21\xba\x11\xaa\x12\x86\x2e\x2Line truncated
|
||||
|
||||
func confLocaleLocale_jaJpIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4459,12 +4459,12 @@ func confLocaleLocale_jaJpIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_ja-JP.ini", size: 52339, mode: os.FileMode(493), modTime: time.Unix(1446027988, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_ja-JP.ini", size: 54219, mode: os.FileMode(493), modTime: time.Unix(1448150140, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_lvLvIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xcb\x6e\x1c\xc7\x96\xe0\x5e\x80\xfe\x21\xad\x86\x60\x1b\xa0\xca\xb0\x3d\x2f\x18\x2e\x7b\x64\xcb\xd7\x97\x96\x44\xb3\x4d\x59\x8b\x36\x84\x72\x54\x65\xb0\x18\xac\x7c\xd4\xcd\xc8\xa4\x4c\x36\x1a\x18\x6d\x7a\x35\x3b\x6d\x66\x79\x35\x77\x37\xfa\x80\xbb\x18\x8f\x57\xc5\x1f\x99\x2f\x99\xf3\x8a\x67\x66\x51\xf4\xbd\x3d\xed\x85\xc5\xca\x8c\xc7\xc9\x13\x27\x4e\x9c\x77\xa8\xed\x76\x51\x6a\xbb\x9a\x3f\x37\x7a\x5d\x99\x62\xb8\xb2\xfd\xee\x55\xb9\x7b\x55\xdb\xe2\x5b\xd3\x17\x56\x77\x17\xc6\xda\x83\x62\xa3\x6c\xd1\xa9\x0d\xbc\x7d\xdb\xdb\xe2\x42\x55\x2d\x34\x2a\xbe\x6d\xef\xde\xb9\x7b\xe7\xac\xad\xf5\xfc\x64\xf7\x6a\x33\xd4\xf6\xee\x9d\x52\xd9\xb3\x65\xab\xba\x72\x7e\xd8\x9c\xb6\x5b\xd5\xe8\xca\xc0\x63\xfd\xcb\xb6\x6a\x3b\x3d\x3f\xbc\xda\xee\x5e\xe3\x28\xd0\x4f\x57\xdb\xf9\xb1\xaa\x76\x6f\xcb\xab\xdd\xdb\xa5\xba\x7b\xc7\x9a\x75\xb3\x30\xcd\xfc\xd8\x68\x37\x99\xd1\x56\x9e\xb7\x43\x0f\xbd\xc7\xcf\x87\x2d\xb6\xef\xb5\xd9\xf0\xc3\x4e\xaf\x8d\xed\x75\x37\xff\x41\xef\xfe\x02\x7f\x75\x30\x1f\xbd\x78\xa9\x97\xd6\xf4\x7a\xfe\x74\xf7\xea\x1c\x3e\xa7\x52\x5b\x98\xf2\x42\x77\xd6\xb4\xcd\xfc\x39\xfe\x7b\x0e\x0f\xb6\x6a\xad\xe7\x4f\xe8\x5d\xaf\xeb\x6d\xa5\xa0\xc7\x89\x5a\xab\x5e\x5d\xe8\xbb\x77\x2a\xd5\xac\x07\x6c\xf1\x1c\x51\x00\x6d\x56\x9d\x86\x16\x8b\x46\xbf\x04\xe0\x2e\xb4\x29\xdb\x7e\x36\x9b\xdd\xbd\x33\x00\xea\x16\xdb\xae\x3d\x35\x95\x5e\xa8\xa6\x5c\xd4\xf8\xf5\x4f\x00\xd0\xb6\xc7\xf9\x0b\x7e\x37\x14\x43\x03\xe8\x34\x1d\xa0\x8f\xbf\x47\x97\x80\x81\x85\xb2\x11\x12\xce\x01\xfc\x62\xb3\x7b\x85\xc8\xc6\x71\x1b\x55\x47\x43\x5d\xec\x5e\x75\x25\x62\xb8\x56\xa6\x9a\x7f\xf3\x60\xab\x6c\x6f\xf1\x3b\xac\x7d\xd9\xc2\x32\x1c\xab\xae\xad\x34\xe2\x65\xd1\x5f\x6e\xb5\xfc\x2e\x54\x0f\x23\x76\x30\x84\x81\x8f\x50\xdb\x7e\x75\xa6\xe6\xc7\xf0\x64\xa9\x86\x12\xa7\x6b\x71\x4c\xec\xb5\x6d\x01\x6b\x6d\x77\x09\xf8\xdc\xb6\x57\xf8\xa7\x39\x87\x57\x6d\xb7\x56\x8d\xb9\x52\x3d\x62\xef\x7b\xfe\xb1\x7b\xb5\x22\x1c\xd6\xa6\xeb\xda\x6e\x7e\xb2\x6d\xd7\x03\xad\x3e\xa0\x67\x81\x23\xcd\xbf\x53\x43\x03\x94\x94\x8c\x84\x2f\x6b\xb3\xee\x10\xd3\xf8\x5e\x15\xf8\xcb\x8d\x85\x6f\x4f\xdb\x6e\x23\x5d\x55\x5f\x22\xc5\xf4\xca\x4c\x0d\x03\x40\xc9\x10\x6d\x06\x91\x6a\x60\xd5\xe8\x3d\x7e\x24\x10\x70\x09\x83\xa4\xad\x60\x0c\x55\xd6\x80\x7c\x22\xda\xf9\x43\xfc\xbb\xf0\x04\xac\x56\xab\x76\x68\xfa\x85\xd5\x7d\x6f\x9a\xb5\x9d\x3f\x6e\x9b\x5e\x15\xb0\x34\xbd\xc2\x35\x1a\x6a\x40\xa4\x7f\x79\x98\x3c\xbe\x6c\x07\x4f\x0c\xf3\x67\xea\xc2\xca\xea\x5b\x79\xe5\xbb\xc1\x3b\x93\x0d\x49\x1f\x66\x17\xa7\x5a\x97\xf4\x69\x43\x3d\x14\xdb\xea\xfa\xad\xad\x91\x58\x87\xaa\x02\xc4\xfe\x69\x80\x2e\x30\xe9\x15\x90\xc0\xf5\xbf\xc2\x7b\xa3\xb7\x9d\xb2\x6e\x08\xd8\xc1\xd0\x60\x7e\xdc\xb5\xcb\x6a\xf7\xba\x56\xb4\xb0\x2b\xd5\xac\xf0\x2b\x7b\xf8\x7f\x8f\x0f\x7e\xb2\x5a\x75\xab\xb3\x17\xf8\x15\xf8\xc7\xfc\xa9\xde\x40\x73\x26\xe7\xbd\x64\x80\xf4\x18\x68\xd1\xca\x64\xf3\xc7\xbb\x5f\xaf\xdf\xd2\xfe\x68\x4b\xf8\x25\xc4\xf4\x93\x69\xe0\xd3\xaa\x0a\xe6\x90\xbf\x80\x49\xe0\xbf\x6e\x99\x7a\xd3\x03\x86\xe2\x67\xb6\xb0\xed\xee\x57\x03\x9f\xd4\xd5\x2d\xac\xb8\xb9\x82\xbf\x55\x05\xdf\xf9\x5b\x0b\x60\x97\xed\x6a\x03\x1b\x0d\x59\x09\xc0\xf1\x9d\x02\x96\xb4\xb6\x05\xec\xf7\x4d\x51\x11\x50\xb6\xb8\xd2\x75\xf1\x88\x9a\x1d\x14\xe6\xaa\x52\x16\x9e\x03\xa3\x03\x8a\xd8\xbd\x5d\x9b\xe2\x73\x55\xf4\xaa\x5b\xeb\x7e\x7e\x6f\xb1\x84\xed\xbd\xb9\x57\x9c\x75\xfa\x74\x7e\xef\xbe\xbd\xf7\xc5\x85\x2a\x81\xd8\x1a\x84\xe3\xf3\x8f\xd4\x17\x07\x04\x06\x6e\x8e\x02\x30\xdd\xec\x5e\xf5\xc5\xf5\x1b\x75\x0e\x9c\x10\x98\xc9\xee\xd5\x7b\x88\xa7\x3f\x0d\xc0\x63\x16\xe5\x92\xf9\x2a\x81\xd3\x68\x58\x90\x95\xd1\xd0\x16\x3a\x3f\xbd\x3c\xf9\xc7\x27\x07\xc5\x71\x6b\xfb\x75\xa7\xe9\x6f\xf8\x1f\x74\xfa\x14\x19\x41\xf1\xcc\x3c\xfa\x0a\x10\x0e\x03\x30\x32\x1e\xa9\x7e\x28\x96\xbb\x57\x57\xb0\x29\x53\xca\xc0\x26\xb8\xa1\xe3\x16\xc8\x81\x2c\xb2\x64\xdb\xc3\x42\xd9\xa1\xb3\xa5\x82\xa5\x9a\x5a\xa8\x11\x83\x80\xf1\x88\xb3\xc4\xe3\x35\xad\x55\x83\x30\xf6\xa5\x43\xf3\x51\xf2\x3d\x06\x51\x09\xa3\xf2\x87\x15\x87\x47\x47\xdf\x3f\xfa\xaa\x28\xaf\x4c\x63\x0a\xd5\xf1\xa9\x01\xfc\xbd\x3e\x1f\x00\x6f\x5b\x60\x78\xfd\xe9\x7f\x59\xac\x75\x03\xdc\xad\x5a\xac\x0c\x7c\xab\xb5\x15\xb0\x47\xa0\x93\x93\x93\x27\xb0\xc4\xd7\xbf\x41\x6b\x02\xb0\x3f\x9b\x7f\xad\x81\x92\xde\x40\x9b\x3f\x55\x88\x57\x81\x20\x47\x58\x51\x46\x30\x9f\x02\x1f\x54\xc0\x28\x3a\x05\x00\xc2\x22\x01\x56\xe0\xb3\x15\x4c\xa4\xbb\x6e\x01\x3c\xbd\xbf\xc4\xf5\xa1\xe1\xd3\x4f\x69\xda\x0e\x4f\x3f\x60\x0c\x37\x4e\x90\x0f\x3d\xc8\xd0\xa6\x01\xbe\x62\x4a\x58\x38\x87\xca\x51\x57\x8f\x4f\x20\x8a\x0b\xc0\x8e\x85\x77\x78\x40\x15\xd6\xd4\xcb\xb6\x1a\x6c\x71\x6f\x76\x0f\xcf\x84\x7b\x0f\xee\xc1\xa8\x4d\xbb\x60\x96\x84\x27\x48\x69\xac\x5a\xc2\x69\xc2\x07\x5c\xc7\x9c\xd7\x1f\x72\xb8\x5b\x06\x19\x55\xf5\x16\x76\xee\xba\xc7\x73\xbb\xde\xbd\xee\x8a\x46\x5d\xc0\x3a\xf1\xf1\x04\xf0\xe3\x90\x3c\x44\xdb\x29\x58\x95\xa6\xb7\x09\x76\x1c\x1b\x14\x02\x79\x38\x6e\xaf\xea\xc2\x74\x05\xf0\x13\xb3\x86\x5d\x60\x0a\xd8\x03\x8c\x3c\x38\xd8\x88\x9c\x66\xb8\xdf\xdd\x2a\x3Line truncated
|
||||
var _confLocaleLocale_lvLvIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\xcb\x72\x1c\x47\x96\x20\xba\xcf\xaf\x08\xb2\x4c\x26\xc9\x0c\x4c\x9a\xa4\xbe\x77\xc6\x64\x4a\x69\x28\x52\xc5\x82\x48\x42\x68\x82\xc5\x36\x6b\x19\x2d\xe5\x99\xe1\x48\x38\x32\x32\x22\x2b\x3c\x02\x10\xd0\xd6\x66\xc3\x4d\xaf\x66\xc7\xcd\x2c\x8b\x53\xbb\xe1\x07\xd4\x62\x34\x5a\x25\x7e\x64\xbe\x64\xce\xcb\x9f\x11\x09\x52\x55\x3d\xad\x85\x88\x0c\x7f\x1f\x3f\x7e\xfc\xbc\x5d\x6d\xb7\xf3\x52\xdb\xe5\xec\xa5\xd1\xab\xca\x14\xfd\xb5\xed\x76\xaf\xcb\xdd\xeb\x8d\x2d\x1e\x9b\xae\xb0\xba\xbd\x30\xd6\x1e\x14\x6b\x65\x8b\x56\xad\xa1\xf4\x5d\x67\x8b\x0b\x55\x35\x50\xa9\x78\xdc\x4c\x26\x67\xcd\x46\xcf\x4e\x76\xaf\xd7\xfd\xc6\x4e\x4a\x65\xcf\x16\x8d\x6a\xcb\xd9\x61\x7d\xda\x6c\x55\xad\x2b\x63\x27\xfa\xe7\x6d\xd5\xb4\x7a\x76\x78\xbd\xdd\xbd\xc1\x1e\x26\x67\xba\xda\xce\x8e\x55\xb5\x7b\x57\x5e\xef\xde\x2d\xd4\xc4\x9a\x55\x3d\x37\xf5\xec\xd8\x68\x37\x8c\xd1\x96\x3f\x37\x7d\x07\x4d\x07\x9f\xfb\x2d\xd6\xee\xb4\x59\xd3\xb7\x56\xaf\x8c\xed\x74\x3b\x7b\xae\x77\x7f\x81\xbf\x5a\x18\x0a\xbf\x5f\xea\x85\x35\x9d\x9e\x3d\xdb\xbd\x3e\x87\x45\x54\x6a\xab\x26\x17\xba\xb5\xa6\xa9\x67\x2f\xf1\xdf\x73\x35\xd9\xaa\x95\x9e\x3d\xc5\x92\x4e\x6f\xb6\x95\x82\xea\x27\x6a\xa5\x3a\x75\xa1\x27\x95\xaa\x57\x3d\x96\xbf\xc4\x45\xab\xc9\xb2\xd5\x50\x3e\xaf\xf5\x25\x4c\xea\x42\x9b\xb2\xe9\xa6\xd3\xe9\xa4\x07\x50\xcd\xb7\x6d\x73\x6a\x2a\x3d\x57\x75\x39\xdf\xe0\x8a\x9f\xc2\x04\x9b\x0e\x47\x2e\xb8\xac\x2f\xfa\x1a\xc0\x67\x5a\x00\x18\x2d\x43\x97\xb0\xec\xb9\xb2\xd1\xca\xcf\x61\xda\xc5\x7a\xf7\x7a\x42\x9d\xd6\x6a\x13\xf5\x73\xb1\x7b\xdd\x96\x00\xd1\x8d\x32\xd5\xec\xbb\x7b\x5b\x65\x3b\x0b\xd3\xb7\xf6\xb2\x01\xa0\x1f\xab\xb6\xa9\x34\x80\x62\xde\x5d\x6d\xb5\xfc\x2c\x54\x07\x9d\xb5\xd0\xde\x4c\x96\x6a\xdb\x2d\xcf\xd4\xec\x18\x3e\x2c\x54\x5f\xe2\x40\x0d\xf4\x07\x4d\xb6\x0d\x80\xa9\x69\xaf\x00\x7e\xdb\xe6\x1a\xff\x34\xe7\x76\xd2\xb4\x2b\x55\x9b\x6b\xd5\x21\xb8\x7e\xe0\x1f\xbb\xd7\x4b\x04\xda\xc6\xb4\x6d\xd3\xce\x4e\xb6\xcd\xaa\xc7\x5d\x06\x88\xcc\xb1\x9b\xd9\xf7\xaa\xaf\x01\x5b\xe2\x6e\xb0\x6c\x63\x56\x2d\x42\x16\x8b\x55\x81\xbf\xa4\x23\x2c\x3c\x6d\xda\xb5\x34\x54\x5d\x89\x88\xd1\x29\x33\xd2\x09\xcc\x47\x3a\x68\xd2\xc9\xa8\x1a\xf6\x88\x8a\x71\x6d\x80\xa0\x25\x74\x91\x56\xb2\x13\x55\x6e\x00\xd8\x84\x96\xb3\x07\xf8\x77\xe1\x50\x54\x2d\x97\x4d\x5f\x77\x73\xab\xbb\xce\xd4\x2b\x3b\x7b\xd2\xd4\x9d\x2a\x60\x27\x3a\x85\x5b\xd2\x6f\xcc\xc4\x97\x1d\xc6\x5f\xaf\x9a\xde\x6f\xfc\xec\x85\xba\xb0\xb2\xd3\x96\x4b\x7c\x23\x28\x32\x69\x7f\xb8\x20\x3b\x3f\xd5\xba\xa4\x25\xf5\x9b\xbe\xd8\x56\x37\xef\xec\x06\x10\xb2\xaf\x2a\x00\xe6\x9f\x7a\xa8\x0f\xe3\x5d\xc3\x76\xdf\xfc\x1b\x14\x1b\xbd\x6d\x95\x95\xf6\x70\x30\xa1\x7c\x76\xdc\x36\x8b\x6a\xf7\x66\x03\xeb\x83\xfd\xad\x97\xb8\xb6\x0e\xfe\xdf\x4d\x26\x3f\x5a\xad\xda\xe5\xd9\xab\x09\xff\x3b\x7b\xa6\xd7\x50\x93\xf0\x75\xdf\x86\x23\xca\x05\x74\xb3\x3c\xc8\xec\xc9\xee\x97\x9b\x77\x88\xfc\x4d\x09\x3f\x08\x63\x7e\x34\x35\xac\xa5\xaa\x5e\x4d\xe4\x0f\x38\xf2\xf8\xaf\x6c\x48\x67\x3a\x80\x47\xfc\xc9\x16\xb6\xd9\xfd\x62\x60\x11\xed\xa6\x81\x9d\x35\xd7\xf0\xb7\xaa\x60\x65\xbf\x36\xdd\xa4\x6c\x96\x6b\x38\x41\x48\x18\x60\x02\xdf\x2b\xa0\x2d\x2b\x5b\xc0\x09\x5e\x17\x15\xcd\xc6\x16\xd7\x7a\x53\x3c\xa2\x6a\x07\x85\xb9\xae\x94\x85\xef\x40\xb1\x60\xeb\x77\xef\x56\xa6\xf8\x4a\x15\x9d\x6a\x57\xba\x9b\xdd\x9d\x2f\xe0\xd4\xae\xef\x16\x67\xad\x3e\x9d\xdd\xfd\xc8\xde\xfd\xfa\x42\x95\x80\x53\x35\x4e\xe3\xab\xfb\xea\xeb\x03\x9a\x05\x22\x7f\x01\xa0\xad\x77\xaf\xbb\xe2\xe6\xad\x3a\x07\x92\x06\xf4\x61\xf7\xfa\xce\x04\x61\x0f\x54\x63\x5e\x2e\x98\x3e\xd2\x6c\x6a\x0d\x1b\xb0\x34\x1a\xaa\x42\xdb\x67\x57\x27\xff\xf8\xf4\xa0\x38\x6e\x6c\xb7\x6a\x35\xfd\x0d\xff\x83\x46\x5f\xe0\x01\x2f\x5e\x98\x47\xdf\x4e\x27\xd0\x9e\x21\xf1\x48\x75\x7d\xb1\xd8\xbd\xbe\x86\x23\x97\x60\x01\xd6\xc0\xc3\x1a\x57\x40\xb2\x62\x81\xb2\xda\x0e\x36\xc7\xf6\xad\x2d\x15\x6c\xcf\x70\x73\xf2\x83\x0f\x7d\x11\xb9\x88\xfb\xaa\x1b\xab\x7a\x26\xce\x0b\x07\xde\xa3\x64\x21\x06\x41\x08\x5d\xf2\x8a\x8a\xc3\xa3\xa3\x1f\x1e\x7d\x5b\x94\xd7\xa6\x36\x85\x6a\x99\xec\x03\x95\xde\x9c\xf7\x00\xaf\x2d\x50\xb0\xee\xf4\x3f\xcf\x57\xba\x06\x7a\x55\xcd\x97\x66\x3a\xb1\xb6\x02\x72\x07\x98\x71\x72\xf2\x14\x36\xf6\xe6\x57\xa8\x8c\x93\xeb\xce\x66\x0f\x35\x60\xce\xdb\x89\xfd\x53\x85\xd0\x94\xe1\x73\x30\x15\x65\x34\xdf\x53\xa0\x6c\x0a\x88\x40\xab\x60\x76\xb0\x33\x00\x0d\x58\xb0\x9a\x4e\x74\xdb\xce\x81\x3a\x77\x57\xb8\x29\xd4\x79\xba\x8c\xba\x69\xf1\xea\x82\x53\x7f\x6b\xff\x79\xcf\x3d\xf7\x6c\x6a\xa0\x19\xa6\x84\xdd\x72\x40\x1c\xb4\xf4\x90\x04\x44\xb8\x00\xc0\x58\x28\xc3\x5b\xa6\xb0\x66\xb3\x68\xaa\xde\x16\x77\xa7\x77\x91\xbe\xdf\xbd\x77\x77\x3a\xa9\x9b\x39\x93\x1b\xbc\x0c\x4a\x63\xd5\x02\x2e\x06\xbe\xa4\x5a\x26\xa7\xfe\xa2\xc2\xe3\xd1\x4b\xa7\xaa\xb3\x70\x44\x57\x1d\x5e\xb9\x9b\xdd\x9b\xb6\xa8\xd5\x05\xec\x10\x5f\x34\x30\x7b\xec\x92\xbb\x68\x5a\x05\xfb\x51\x77\x36\x06\x8d\xa3\x70\x82\x17\x0f\x86\xd5\xd5\xa6\x30\x6d\x01\x24\xc3\xac\x00\xed\x4d\x01\x48\xcf\x90\x83\x2b\x8a\xb0\x68\x3a\x99\xb8\xcd\x65\xd4\x25\xdc\x07\x36\x00\x0e\x46\x8b\x27\x4d\xa7\x08\x8c\x3Line truncated
|
||||
|
||||
func confLocaleLocale_lvLvIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4479,12 +4479,12 @@ func confLocaleLocale_lvLvIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_lv-LV.ini", size: 49397, mode: os.FileMode(493), modTime: time.Unix(1446027994, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_lv-LV.ini", size: 49332, mode: os.FileMode(493), modTime: time.Unix(1448150156, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_nlNlIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\xbd\x6b\x8e\x1b\x57\x96\x27\xfe\x5d\x80\xf6\x70\xad\x82\x5a\x36\x90\x49\xb5\xed\xff\x1f\xd3\x30\x4c\x7b\x24\xa5\x4a\x52\x5b\x8f\x1c\xa5\x64\x63\xca\x30\xe8\x20\xe3\x92\x0c\x31\x18\xc1\x8a\x07\xd3\xa9\x46\xef\x60\x16\x30\x7b\xe8\x2d\xcc\xb7\xda\xc9\xac\x64\xce\xef\x9c\x73\x5f\x11\xc1\x94\xaa\x1a\x83\x81\x61\x25\xe3\xbe\x1f\xe7\x9e\x7b\xde\x37\x3b\x1c\x16\xb9\x6d\x57\xf3\xa7\xb6\x32\xd6\x56\xc7\xba\xcf\x8b\x8d\x35\x1f\x6d\xb9\xde\xd8\x6d\xdd\x76\xd6\x3c\x2b\x3a\xd3\xda\xe6\x58\xac\xac\xd9\x50\xd9\x6d\x63\x8f\x54\xba\xa8\xcc\xb3\xfa\xee\x9d\xbb\x77\xb6\xf5\xde\xce\x9f\xf7\x45\x7b\xf7\x4e\x9e\xb5\xdb\x65\x9d\x35\xf9\xfc\xc2\xfd\xba\x7b\xc7\xfe\x71\x28\xeb\xc6\xce\x7f\xb6\xcd\xce\x56\x95\xad\xa8\x8a\x2d\x0f\xf3\xe7\xf4\xcf\xdd\x3b\x6d\xb1\xa9\x16\x45\x35\x7f\x51\x95\xf5\x66\x83\x4c\x4e\xa9\xfb\x6e\xfe\x68\xbd\xb7\x65\xee\x93\xfa\xc3\xfc\x51\x56\xb9\xa4\xc6\x6e\x0a\x1a\x5d\x33\x7f\xcb\x3f\x1a\x6b\x9b\xbb\x77\xae\xed\xb2\x2d\x3a\x3b\xff\x45\xfe\xde\xbd\x73\xb4\x4d\x5b\xd4\x15\xfa\x6e\x0b\xfa\x3e\x64\x1b\x3b\xbf\xcc\x36\x45\x95\xdd\xbd\xd3\xd9\xfd\xa1\xcc\xa8\xf8\xd5\x87\x6c\x59\xd6\x35\xb5\x5a\x66\xd5\xa6\x47\x99\x77\x59\x56\xde\xbd\xb3\x6a\x2c\xe5\x2f\x2a\x7b\x3d\x7f\xc2\x3f\x67\xb3\xd9\xdd\x3b\x3d\xad\xc6\xe2\xd0\xd4\xeb\xa2\xb4\x8b\xac\xca\x17\x7b\x4c\xef\x99\x5d\x36\x7d\xb1\xa3\x8e\x38\xcb\x96\x86\x16\x69\xcf\xc3\xc2\xf0\x6d\x4e\xb3\x5c\x64\x2d\xe6\xb0\xb1\x98\x85\xc9\xca\x16\xeb\x87\xe6\xaa\x6c\x1f\xb7\x50\x65\xd9\x9e\x16\x6e\x9f\x15\xe5\xfc\xe9\x39\xfe\x60\xe8\x6d\x7b\x5d\xd3\xd2\xfe\x92\xad\xb6\x5d\x77\x5d\xd7\x58\xdc\xc6\x2e\xba\x9b\x03\x2f\x6e\xb1\x2e\x56\x59\x87\x59\xae\xb2\x43\xb7\xda\x66\xf3\x27\x8f\x2e\xdf\x3d\x79\xfe\x08\x9d\x34\xf6\x50\xd3\x92\xd4\xcd\x0d\x2d\x98\xfe\x44\xd1\xba\xd9\x64\x55\xf1\x91\xea\xd1\x2a\xbd\xe1\x8f\x56\x1a\xd9\x17\x4d\x53\x37\xf3\xab\x43\x61\x37\x96\xfa\xa7\x45\x58\xa0\x95\xf9\xeb\xc2\xf6\xd7\xd6\x34\x71\x33\xc8\xdc\x17\x9b\x06\xab\xa9\xf9\xf2\xe9\x32\xd7\x75\xb3\x73\x39\x47\xfa\x6d\xfc\x28\x6e\xa4\x00\x0d\xc4\xe5\xd7\xc9\x30\xb2\x8a\xf6\x83\xb3\x1f\xdb\x2d\x2d\x67\x9c\x4d\xeb\x97\xe5\x7b\x5a\xd7\x43\x56\xd9\x72\xfe\x08\xbf\xf1\x13\xe3\xcd\x56\xab\xba\xaf\xba\x45\x6b\xbb\xae\xa8\x36\xb4\xf0\x92\x50\x54\x04\x36\x65\x49\x49\x0c\x59\x2e\xf7\x45\x92\x7c\x53\xf7\x7e\x8b\xe7\xef\xaf\x8d\x6e\xa9\x66\xf8\x4a\x94\x93\x36\xc7\x73\x69\x17\x6b\x6b\x73\x99\x4d\x8b\x9f\xb4\x79\x7d\x59\xd2\xea\xfd\xb5\xb7\x6d\xd7\xce\x2f\xe9\xeb\x3c\xcb\xaa\x63\x93\x71\xad\xa2\x6d\x29\x63\xfe\xd3\x35\xe5\xf2\xa4\xb0\x85\xd5\x0a\x33\xaa\xaa\xbe\x64\x18\xba\x7b\xe7\xd7\xd6\x66\xcd\x6a\xfb\x1b\x06\x8d\x1f\xf3\xbf\xd4\x96\x0e\x14\x83\x64\xb4\xbd\x6f\x0e\x6d\x99\x6d\x08\xb0\xb3\xae\x15\xe0\x0a\x80\xa5\x5d\xcd\x2f\x9b\x7a\x49\xcd\x12\x8c\xad\xea\xdc\xce\x9f\xd0\x3f\xdc\x03\x26\x93\x95\x25\x75\xa1\xbf\x78\x59\xe8\xaf\xec\x45\x57\x74\xb4\x1c\x51\x12\xfd\x38\x1c\x08\xcc\x8f\x04\x8c\x26\xb7\x84\x41\x1a\x60\x8c\x1d\x6f\xd3\x81\x72\x9b\x0e\xf3\xcb\xeb\x15\x75\xbe\xc0\xa9\xa7\xd1\xbc\x58\x1b\x5a\xc5\x07\x0d\x81\x50\x5f\x55\xb4\x70\x84\x47\x36\x2d\x16\xb2\xa0\x26\x2e\xb8\xec\x99\x39\x94\x36\x6b\x01\x65\x59\x6e\xbe\xcf\x0c\x35\xb5\xb1\xdd\xfc\xde\x62\x49\x47\x74\x77\xcf\x10\x12\x5a\xcf\xef\xdd\x6f\xef\xfd\xf0\xac\xa7\x6a\xb4\xfe\xb6\xfd\xfe\x61\xf6\x83\x59\x65\x94\x43\xeb\x7b\x63\x96\x96\x80\xce\xa2\x2f\x43\x87\x81\xf6\xc7\x64\xd5\x4d\xb7\x45\x87\x84\xbb\xe8\x47\x6b\x80\x0e\xbe\xc0\xea\xfd\xb5\x27\x74\xb1\xc8\x97\x82\x0c\x79\x3c\x9c\xd8\xd8\xd6\xbc\xba\xb9\xfa\x6f\x2f\xcf\xcc\x25\x21\xc3\x4d\x63\xf9\x37\xfd\x43\xe5\xbf\x25\x50\x34\xef\x8a\x8b\xc7\xb4\x01\x54\x55\x56\xe7\x22\xeb\xb2\x25\x46\x9e\x02\x06\xf2\x71\x4e\x5d\xf6\x39\xbe\x80\x3b\xdb\x6e\xfe\x9c\xfe\x19\xee\x94\xa2\x80\xf4\xd0\xeb\x99\xa7\xb6\x18\x5f\xf8\xae\xa4\x2c\x25\xeb\x0a\x6b\x2b\xe6\x45\x55\xd5\x17\x8f\x09\x0f\x11\xbe\xa3\x03\x69\x3b\xd3\x77\xeb\x7f\x59\xd0\x80\x6c\x93\x95\x8b\x55\x61\x76\x59\x93\xed\x08\x8b\x12\x44\xcb\x26\xf2\x64\x69\x3e\x6d\x5b\x12\x5a\x23\xd8\xb8\xba\x7a\x79\x4e\x3f\xfa\x16\x83\xe9\xb6\x84\x3c\x69\x04\xed\x5f\x4b\xac\x97\x76\xf7\x6e\x6b\x0d\x0e\x8a\x41\x01\x53\xaf\x87\xcb\x63\x72\x1d\x28\xb5\x6b\x9b\x66\x41\x68\xb7\xbb\xc1\x62\x73\x83\xa7\x0a\x4b\x6b\x74\x0e\xaa\xba\xa3\xbd\x34\x5c\x4b\x5b\x28\xaa\x63\x56\x16\x39\x2d\xb9\x5b\x8b\xb4\x2a\x92\x4c\x5e\xd3\xe6\xa1\x32\x01\x6b\x7d\x0d\x18\x68\xb2\x15\xe6\x6a\xee\xcd\xee\x11\x2c\xe4\xe6\xde\xf9\x3d\x6a\xb0\xaa\x17\x82\x44\x80\xca\x73\x42\x2c\x74\x34\x16\x72\xbd\x34\x82\x17\xff\x3b\x40\x48\x06\xa2\xf9\x26\xce\x37\xd7\x45\xb7\xa5\x0b\xcb\xf0\x75\x01\xf8\xca\x2a\xc3\x4d\x1a\x45\x42\xc9\xc4\x1d\xc6\xd2\x9d\x65\xa4\x65\xdc\xe7\xc4\x84\xef\xde\x71\x1b\x26\x10\xf6\xae\xb6\x28\xcd\xfd\x94\xb8\x4a\xaa\x21\xb0\xe1\x56\xe7\x55\x79\x74\x38\x94\x72\x29\x08\x88\xb8\x0c\xb7\x73\x97\x8c\x25\xcc\xb6\xa0\xf3\xfa\x21\x41\xbc\x28\x8f\x63\xb2\x69\x6a\x3a\xd0\x25\x21\x3c\x5a\xb8\x2f\x04\xd1\xc8\xbe\x45\xd7\x48\x6b\x68\xd5\xe9\x38\xe5\x74\x5e\x56\x7a\x23\xf8\x82\xae\xaf\x47\x25\x2f\xdb\xbe\x56\x92\xa2\x89\xeb\x63\xea\x42\x53\xe4\xfLine truncated
|
||||
var _confLocaleLocale_nlNlIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\xbd\x6b\x8e\x1c\x47\x92\x27\xfe\x3d\x4f\xe1\x62\x83\x7f\x92\x40\x55\x72\xa4\x9e\x3f\x76\x20\x28\xa9\x25\x59\x14\xc9\x11\x1f\xb5\x2c\x52\xc2\xb6\x40\xa4\x22\x33\xbc\x32\x83\x19\x19\x91\x1d\x8f\x2c\x15\x07\x73\x83\x3d\xc0\xde\x61\xae\xb0\xdf\xfa\x26\x7b\x92\xb5\x9f\x99\xf9\x2b\x22\xaa\xc8\xee\xc1\x62\x05\x88\x95\xe1\xef\x87\xb9\xb9\xbd\x3d\x3b\x1c\x96\xb9\x6d\xd7\x8b\x67\xb6\x32\xd6\x56\xc7\xba\xcf\x8b\x8d\x35\x9f\x6d\x79\xb9\xb1\xdb\xba\xed\xac\x79\x5e\x74\xa6\xb5\xcd\xb1\x58\x5b\xb3\xa1\xb2\xdb\xc6\x1e\xa9\x74\x51\x99\xe7\xf5\x6c\xb6\xad\xf7\x76\xf1\xa2\x2f\xda\x59\x9e\xb5\xdb\x55\x9d\x35\xf9\xe2\xcc\xfd\x9a\xd9\x3f\x0e\x65\xdd\xd8\xc5\x2f\xb6\xd9\xd9\xaa\xb2\xd5\x6c\x6b\xcb\xc3\xe2\x05\xfd\x33\x6b\x8b\x4d\xb5\x2c\xaa\xc5\xcb\xaa\xac\x37\x1b\xca\xe2\x84\xba\xef\x16\x8f\x2f\xf7\xb6\xcc\x5d\x4a\x7f\x58\x3c\xce\x2a\x4d\x69\xec\xa6\xa0\x41\x35\x8b\x77\xfc\xa3\xb1\xb6\x99\x5d\xd9\x55\x5b\x74\x76\xf1\xab\xfc\x9d\x1d\x6d\xd3\x16\x75\x85\x4e\xdb\xc2\xce\x0e\xd9\xc6\x2e\xce\xb3\x4d\x51\x65\xb3\xce\xee\x0f\x65\x46\x65\x2f\x3e\x65\xab\xb2\xae\xab\x59\x99\x55\x9b\x1e\x25\xde\x67\x59\x39\x5b\x37\x96\x72\x97\x95\xbd\x5a\x3c\xe5\x9f\xf3\xf9\x7c\xd6\xd3\xec\x97\x87\xa6\xbe\x2c\x4a\xbb\xcc\xaa\x7c\xb9\xc7\x94\x9e\xdb\x55\xd3\x17\x3b\xea\x83\xb3\x6c\x69\x68\x51\xf6\x18\x0f\x46\x6d\x73\x9a\xda\x32\x6b\x31\xf4\x8d\xc5\xe0\x4d\x56\xb6\x33\x6e\xab\xca\xf6\x71\xf5\x2a\xcb\xf6\x33\xbb\xcf\x8a\x72\xf1\xec\x14\x7f\x68\xc4\x6d\x7b\x55\xd3\x42\xfe\x9a\xad\xb7\x5d\x77\x55\xd3\x6f\x9a\xf9\xb2\xbb\x3e\xf0\x52\x16\x97\xc5\x3a\xeb\x68\x6a\xeb\xec\xd0\xad\xb7\xd9\xe2\xe9\xe3\xf3\xf7\x4f\x5f\x3c\x9e\x51\xa1\x43\x4d\x6b\x50\x37\xd7\xb4\x40\xfa\x93\xca\xd5\xcd\x26\xab\x8a\xcf\x54\x87\x96\xe5\x2d\x7f\xb4\xdc\xc0\xbe\x68\x9a\xba\x59\x5c\x1c\x0a\xbb\xb1\xe5\x8c\xe6\xbd\x44\x13\x8b\x37\x85\xed\xaf\xac\x69\xa2\x36\x90\xb7\x2f\x36\x0d\x56\x4f\xb3\xe5\x53\xf3\x2e\xeb\x66\xe7\x32\x8e\xf4\xdb\xf8\x01\x5c\x73\x3e\x8d\xc1\x65\xd7\xf1\x08\xb2\x8a\x56\x9f\x73\x9f\xd8\x2d\x2d\x5f\x9c\xdb\xce\xb2\x7c\x4f\xeb\x78\xc8\x2a\x5b\x2e\x1e\xe3\x37\x7e\xd2\x48\xb3\xf5\xba\xee\xab\x6e\xd9\xda\xae\x2b\xaa\x0d\xad\xb3\x24\x14\x15\x41\x47\x59\x52\x12\xc0\xc7\x65\xbe\x8c\x53\xaf\xeb\xde\x6f\xe7\xe2\xc3\x95\xd1\xed\x93\x74\x5f\x85\x32\x92\xb6\x30\x87\x76\x79\x69\x6d\x2e\xb3\x68\xf1\x73\x76\xe8\xcb\x92\x56\xec\xaf\xbd\x6d\xbb\x76\x71\x4e\x5f\xa7\x59\x56\x1d\x9b\x0c\x55\x8a\xb6\xa5\xf4\xc5\xcf\x57\x94\x89\xb9\xd0\x76\x55\x6b\xcc\xa3\xaa\xfa\x12\x80\x32\xfb\xad\xb5\x59\xb3\xde\x7e\x9c\xc9\xdf\xc5\x5f\x6a\x4b\xc7\x04\x30\x17\x6d\xe4\xdb\x43\x5b\x66\x1b\x02\xdb\xac\x6b\x19\x7e\x02\xec\x48\x17\x8b\xf3\xa6\x5e\x51\x83\xfb\xd9\xba\xce\xed\xe2\x29\xfd\x43\x4d\x63\xf4\x59\x59\x7e\x9c\xe9\x0f\x5e\x04\xfa\xcb\xab\xde\x15\x1d\x4d\x3e\x4a\xa1\x1f\x87\x03\x01\xf0\x91\x60\xcd\xe4\x96\x70\x41\x83\xb3\xbf\xe3\x0d\x39\x50\x6e\xd3\xd1\x8c\xf2\x7a\x4d\xbd\x2e\x71\x86\x69\x18\x2f\x2f\x0d\xad\xd9\xbd\x86\xe0\xa4\xaf\x2a\x5a\x26\x42\x08\x9b\x16\xcb\x56\x50\x0b\x67\x5c\xf6\xc4\x1c\x4a\x9b\xb5\x00\xa5\x2c\x37\x3f\x64\x86\x5a\xda\xd8\x6e\x71\x67\xb9\xa2\x93\xb7\xbb\x63\x08\x9b\x5c\x2e\xee\xdc\x6d\xef\x3c\x7a\xde\x53\x35\x5a\x6d\xdb\xfe\xf0\x30\x7b\x64\xd6\x19\xe5\xd0\x82\x5e\x9b\x95\x25\xd8\xb2\xe8\xcb\x10\xb0\xd3\x6e\x98\xac\xba\xee\xb6\xe8\x90\x90\x10\xfd\x68\x0d\xce\xf8\x37\x33\x6c\x04\x21\x80\x65\xbe\x12\xa4\xc6\xc3\xe1\xc4\xc6\xb6\xe6\xf5\xf5\xc5\x7f\x7b\x75\x62\xce\x09\xa9\x6d\x1a\xcb\xbf\xe9\x1f\x2a\xff\x67\x02\x39\xf3\xbe\x38\x7b\x32\x9f\x51\x4d\x59\x9a\xb3\xac\xcb\x56\x18\x77\x02\x04\xc8\xc6\x19\x74\xb9\xa7\xf8\x9a\x01\x4d\x2e\x5e\xd0\x3f\x83\xdd\x91\x93\x9d\x1e\x66\x39\xcb\xd4\x0e\x23\x01\xdf\x0b\x97\xa4\x54\x5d\x59\x6d\xc2\xbc\xac\xaa\xfa\xec\x09\x21\x16\x42\x5e\x74\xdc\x6c\x67\xfa\xee\xf2\x5f\x96\x34\x14\xdb\x64\xe5\x72\x5d\x98\x5d\xd6\x64\x3b\xc2\x87\x04\xb7\xb2\x77\x3c\xcb\xf9\xac\x6d\x4b\x42\x53\x04\x0d\x17\x17\xaf\x4e\xe9\x47\xdf\xd2\x40\xba\x2d\xe1\xc1\x7c\xd6\xfe\xb5\xc4\x2a\x69\x5f\xef\xb7\xd6\xe0\x28\x18\xe4\x9b\xfa\x72\xb8\x28\x26\xd7\x41\xce\x67\xb6\x69\x96\x84\x40\xbb\x6b\xac\x30\x37\x77\x53\x59\x69\x8c\xe0\xbd\xaa\x3b\xda\x3f\xc3\xb5\xa4\x81\xa2\x3a\x66\x65\x91\xd3\x3a\xbb\x55\x48\x6b\x22\xc9\xe4\x35\x6d\x18\xea\x12\x78\xd6\x57\xd8\xf6\x26\x5b\x63\x9a\xe6\xce\xfc\x0e\x6d\x7f\x6e\xee\x9c\xde\x99\xcf\xaa\x7a\x29\x08\x02\x58\x39\x27\x9c\x41\xc7\x60\x29\x57\x44\x23\xc8\xee\xbf\x03\x68\x64\x18\x9a\x6f\xe2\x7c\x73\x55\x74\x5b\xba\x72\x0c\x23\x7e\x40\x54\x56\x19\x6e\xd2\x28\x86\x89\x67\xed\x90\x91\x6e\x28\xe3\x23\xe3\x3e\xc7\xb3\x9d\xb9\x7d\x12\x90\x7a\x5f\x5b\x94\xe5\x4e\x4a\xdc\x09\xd5\x00\xba\x70\x19\xf3\x82\x3c\x3e\x1c\x4a\xc1\xf0\x0c\x17\x2e\xdd\xed\xd8\x39\xa3\x02\xb3\x2d\xe8\x6c\x7e\x4a\xb0\x29\x8a\xe3\x4c\x6c\x9a\x9a\x0e\x6f\x49\xb8\x8c\x96\xec\x1b\x46\x26\xb2\x5f\xd1\xa5\xd0\x1a\x5a\x6e\x3a\x3a\x39\x1d\x8e\xb5\xa0\x78\x5f\xce\xf5\xf4\xb8\xe4\xf5\xda\xd7\x4a\x06\x34\x71\x75\x4c\x5a\xe8\x80\xdc\x7e\xb6\x51\x3b\x84\x30\x88\x4e\x28\x19\x07\x12\x66\x58\xf2\xa9\xf8\x50\x74\xc7\xda\x36\xb6\xca\x4Line truncated
|
||||
|
||||
func confLocaleLocale_nlNlIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4499,12 +4499,12 @@ func confLocaleLocale_nlNlIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_nl-NL.ini", size: 45968, mode: os.FileMode(493), modTime: time.Unix(1446027990, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_nl-NL.ini", size: 45884, mode: os.FileMode(493), modTime: time.Unix(1448150144, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_plPlIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\xcd\x8e\x1c\x47\x92\x27\x7e\x27\xc0\x77\x70\x55\x83\x90\x04\x94\x52\x50\xeb\xff\xdf\x5d\x68\x95\xd2\x52\xa4\x5a\x62\x8b\x5f\xcb\x2a\x82\x18\x09\x42\x2a\x32\xc2\x2b\x2b\x2a\x33\x23\xb2\xe3\x83\xa9\xc8\xc1\x1c\x56\x68\x61\x2f\xfb\x00\xab\xd1\x65\xde\xa1\x4f\xdb\xa3\xd3\x34\xeb\x45\xf6\x49\xd6\x7e\x66\xe6\x1e\xee\x11\x91\x45\x4a\xd3\x98\x4b\x55\x86\x7f\xbb\xb9\xbb\x7d\xb9\x99\x79\xb2\xdb\x2d\x32\x5b\xa7\xf3\xcf\xec\x61\x59\x6e\x6c\x5d\x24\xa6\xad\xaf\x7f\x68\x57\x89\xf9\x22\x6f\x4c\x91\xec\xf2\x3a\xa1\xc4\xbd\xf9\xa2\x34\xd9\x21\x4f\xae\x7f\x48\xae\x5e\xfd\x98\x26\x06\x89\xf4\x51\x17\xdd\xd6\xd4\xb6\xda\xdb\xea\x60\x6f\xdf\xba\x7d\xeb\xb2\xdc\xda\xf9\x59\x53\x95\x54\x60\x75\xfd\xc3\xdf\xfe\xb2\x2f\x92\xdb\xb7\xb2\xa4\xbe\x5c\x96\x49\x95\xcd\x9f\xb6\x9b\x5d\xde\xdc\xbe\x65\xbf\xdf\x6d\xca\xca\xce\x9f\x64\xeb\xaa\xdb\x27\x57\x54\xd3\x6e\x76\xf3\xa7\xe5\xb6\x4c\x6f\xdf\xaa\xf3\x55\xb1\xc8\x8b\xf9\xd7\xc9\xa6\x5c\xb5\x57\xa6\xce\x5f\xfd\xa4\xa9\x65\xdb\xcc\x5f\x74\x9c\xac\x29\xed\x8e\xca\x55\xf6\xca\xd6\x4d\xe5\xcb\x56\x76\x95\xd7\x8d\xad\x26\xb2\xf6\x76\x59\xe7\x8d\x1b\xe5\xed\x5b\x2f\x6d\x55\xe7\x65\x31\x7f\x41\xff\xaf\xe8\x7b\x97\xac\xfa\xcc\xc6\x6e\x77\x9b\x04\xa5\x0f\xc9\x72\x53\x16\xb7\x6f\x6d\x92\x62\xd5\xa2\xc8\x1f\x5f\xfd\x74\xe8\xd6\xb7\x6f\xa5\x95\xa5\x02\x8b\xc2\xee\xe7\xcf\x9b\xfd\xdf\xfe\x52\x1d\x66\xb3\xd9\xed\x5b\x2d\x81\x65\xb1\xab\xca\x8b\x7c\x63\x17\x49\x91\x2d\xb6\x98\xef\x53\x4e\x30\xed\xf5\x2f\x5d\xb3\x2e\xf7\x45\xbe\x4e\x4c\x6e\xf6\x34\xb0\xd4\xea\x84\x6c\x46\x53\x5f\x24\xb5\xcc\xbe\xdc\x27\x45\x67\xae\x92\x75\x09\xf0\xa2\xd1\x22\x21\x10\x3f\x4e\x0e\xfb\xc4\x3c\x0f\x9a\x21\x98\x6e\x93\x7c\x33\xff\xfc\x3d\xfc\xc3\x34\xea\x7a\x5f\x12\xc8\xbf\x4c\x68\x49\x4b\x80\x64\xd1\x74\x3b\x3b\x7f\x41\x8b\x7a\x30\xbb\xb2\x40\x3d\x5a\xb4\x34\xd9\x35\xe9\x65\x32\xbf\x27\xff\xd1\x4d\x65\x77\x25\xc1\xa8\xac\xba\xf9\x33\xfa\x79\xe8\xe8\x67\xde\x6e\x6f\xdf\x2a\xab\x55\x52\xe4\x87\xa4\x01\xc0\x9e\xe8\x47\x0a\xa8\x6d\xf3\xaa\x2a\xab\xf9\x23\xfe\x77\xfb\x16\x41\x63\x81\x56\xe6\x8f\xcb\xbd\x35\x55\xd4\x08\xf2\xb6\xf9\xaa\x02\x58\x29\x3b\x31\xfc\xc1\xad\x20\xeb\xa2\xac\xd6\xf3\x3f\xd0\x1f\x5a\xb1\x71\x45\x1a\x81\x54\x2a\xa3\xde\x69\x97\xae\x2c\x67\xd2\x82\x1f\x5e\xfd\x98\x1d\x92\xab\xb0\xc8\x36\xbf\x7d\x2b\xc9\xb6\x04\xd8\x5d\x52\xd8\xcd\xfc\x29\xfe\x1a\x4e\xa1\xea\x49\x9a\x96\x6d\xd1\x2c\x6a\xdb\x34\x79\xb1\xaa\xe7\xcf\xeb\x26\xd9\xe7\xb6\xc8\x13\xb3\x2e\x8b\x86\x8a\x4c\x64\xdd\xbe\xd5\x95\xad\x5f\xe3\xf9\x39\xad\xfd\x95\x91\x2f\xcd\xf2\x95\xce\xf7\xe5\x95\xa5\xb3\xd5\x57\xe5\xd9\xd4\x8b\x0b\x6b\xb3\xf9\x57\x34\xfa\xeb\x1f\x4c\xb2\x6e\xda\x64\x53\x94\xd7\x3f\xa7\x34\xda\x5d\xbb\xd9\x10\x08\xff\xd4\xd2\xe6\xad\xe7\x4f\xd2\x83\x25\x80\xd0\xd9\xb3\xe6\xb0\xcd\x69\x4f\xdc\xbe\x95\xd7\x35\x65\x62\x4b\x2d\x37\x76\xdb\xa1\xcd\x34\x29\x52\x9a\xdd\xdd\xa2\xdd\xe0\x7c\xdc\xbe\xf5\x4d\x6d\x93\x2a\xbd\xfc\x16\x13\xc0\x0f\x3a\x3b\xf5\xa1\x5d\xe7\xb4\xab\x72\xcb\xfb\xf4\xe8\x5a\x63\xaf\xcd\x83\x1d\xa6\x1d\xce\xbf\xa6\x73\x5d\xd6\x07\x2b\x9b\xa7\xcc\xec\xfc\xab\x32\xe3\xbe\xf2\x82\x26\xb8\xd9\x50\x67\xfa\x6b\xfe\x80\xff\xcb\x1a\x35\x79\x43\x50\xfa\xaa\x2a\xd7\xb9\xc9\x35\xbd\xbb\x2a\xac\xc9\x36\x89\xd9\xe5\x84\x44\xa8\xd1\x55\x69\xda\xaa\x4d\x09\x8d\x28\x9c\xb2\x32\x5d\xd3\x41\x02\x76\xa0\xe1\x3c\xb8\x30\x04\xd8\xb7\x2b\xda\x52\x6d\x51\x10\x68\x09\x31\xad\x6a\x34\x97\x67\xd6\xdc\xe7\xb2\xa7\x66\xb7\xb1\x49\x8d\x5d\x97\x64\xe6\xe3\xc4\x34\x49\xb5\xb2\xcd\xfc\x64\xb1\xa4\xb3\xbb\x3e\x31\x97\x95\xbd\x98\x9f\xdc\xa9\x4f\x3e\xf9\xa2\xa5\x6a\x9b\xbc\xb0\xf5\xc7\xef\x27\x9f\x98\x94\x70\xc5\x05\x81\xbd\x33\x4b\x4b\xbb\xd0\xa2\x2f\x43\x47\xa2\x58\x59\x43\x10\x6f\x2e\xd1\x61\x5e\x18\xfa\x51\x1b\xa0\x89\xb7\x00\xbe\x3f\xb5\x84\x4d\x16\xd9\x52\x50\x29\x8f\x87\x13\x2b\x5b\x9b\x47\xdd\xd9\x7f\x7f\x78\x6a\x9e\x96\x75\xb3\xaa\x2c\xff\xa6\x3f\x54\xfe\x43\xda\x9c\xe6\x3c\xbf\xff\x19\xad\x00\x55\x15\xd8\x04\xbb\x6e\x99\x1c\x3a\x93\x51\xa7\xe9\xa5\x14\xc0\xc9\x3d\xef\x76\x71\xc6\x25\xb5\x3b\xff\x92\xfe\x4c\xad\xd6\x08\x01\x50\x33\x01\xee\x18\xf6\xa0\x10\xa6\xdd\x54\x1f\x5e\xfd\xc4\x18\xea\xd5\xff\x24\x94\xb9\x61\x1c\xf5\xe0\xf1\xe3\x27\xf7\x3f\x33\x07\x3a\x0e\x59\xc9\x9b\x67\x6b\xda\xe6\xe2\xbf\x2c\x56\xb6\xb0\x55\xb2\x59\xa4\x39\xaf\x23\x4f\x98\xe6\x54\xd7\x1b\x42\x78\xb4\x37\xce\xab\x6e\x69\xce\xce\x1e\x62\x3c\xcd\xe5\xfc\xfa\x9f\xd3\xdc\x5e\xff\x02\x74\x55\xff\x69\x03\xc0\x69\xbf\xe7\x97\xd6\xe0\x18\x19\x14\x33\xe5\xc5\x10\x4e\x34\xd4\x26\x59\xd2\xb2\x52\xe3\xb6\xaa\x16\x84\x97\x9b\x0e\x50\xe7\x66\x8f\x15\x96\xd6\xe8\x54\x14\x65\x43\x8b\x6a\xb8\x96\xb6\x90\x17\x2f\x93\x4d\x9e\x11\xec\x1d\x60\xe2\xaa\x48\x32\x59\x49\xab\x88\xca\xb4\x9b\xcb\x3d\x36\x03\x61\x2a\xa2\x2b\xb5\x39\x99\x9d\xd0\xa6\xc8\xcc\xc9\x7b\x27\xd4\x60\x51\x2e\x04\xbd\x00\xd3\x67\x44\x35\xe9\x48\x2e\x84\x08\x55\x82\x2d\x9f\x09\x19\xa2\xb3\x90\x1b\x02\x9f\xd9\x96\xd7\xbf\x80\x8a\x76\xd7\x3f\xd0\xa9\x3e\x00\xd8\x4b\x7b\x30\x0d\x2d\xd9\xa1\x47\x3c\x8a\xa2\xa2\x49\x3b\x3c\x16\x2d\xaf\x16\xf4\x4d\x5Line truncated
|
||||
var _confLocaleLocale_plPlIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\xcd\x92\x1b\x47\x92\x27\x7e\xc7\x53\x84\xaa\x8d\x26\xc9\xac\x04\x99\x5a\xff\xff\xee\x9a\x56\x90\x96\x2a\xaa\x25\xb6\x48\x16\x57\x55\x34\xda\x48\x46\x83\x12\x99\x51\xa8\x2c\x00\x19\xe8\xfc\x20\x94\x18\x9b\xc3\xca\x5a\xb6\x97\x7d\x80\xd5\xe8\x32\xef\xd0\xa7\xed\xd1\x69\x9a\xf5\x22\xfb\x24\xeb\x3f\x77\x8f\xc8\xc8\x44\xa2\x48\x69\xda\x86\x07\xa2\x32\xbe\xc3\xc3\xc3\xc3\xdd\xc3\xdd\x23\xd9\x6e\xe7\x99\xad\xd2\xd9\x67\x76\xbf\x70\x6b\x5b\x15\x89\x69\xaa\xdb\x1f\x9a\x65\x62\xbe\xc8\x6b\x53\x24\xdb\xbc\x4a\x28\x71\x67\xbe\x70\x26\xdb\xe7\xc9\xed\x0f\xc9\xcd\xab\x1f\xd3\xc4\x20\x91\x3e\xaa\xa2\xdd\x98\xca\x96\x3b\x5b\xee\xed\x64\x72\xed\x36\x76\x76\x51\x97\x8e\xb2\x97\xb7\x3f\xfc\xed\x2f\xbb\x22\x99\x64\x49\x75\xbd\x70\x49\x99\xcd\x9e\x36\xeb\x6d\x5e\x4f\xec\xf7\xdb\xb5\x2b\xed\xec\x3c\x5b\x95\xed\x2e\xb9\x99\x5c\xdb\xf5\x76\xf6\xd4\x6d\x5c\x3a\xa9\xf2\x65\x31\xcf\x8b\xd9\x37\xc9\xda\x2d\x9b\x1b\x53\xe5\xaf\x7e\x92\x44\xd7\xd4\xb3\xe7\x2d\xa7\x4a\x42\xb3\xa5\x52\xa5\xbd\xb1\x55\x5d\xfa\x92\xa5\x5d\xe6\x55\x6d\xcb\xc3\x9c\x9d\x5d\x54\x79\xed\x47\x37\x79\x69\xcb\x2a\x77\xc5\xec\x39\xfd\xde\x24\x93\x6d\xb2\x0c\x59\xb5\xdd\x6c\xd7\x09\x8a\xee\x93\xc5\xda\x15\x93\x75\x52\x2c\x1b\x14\xf8\xe3\xab\x9f\xf6\xed\x6a\x92\x96\x96\xb2\xe7\x85\xdd\xcd\x9e\xd5\xbb\xbf\xfd\xa5\xdc\x4f\xa7\xd3\x49\x43\x60\x98\x6f\x4b\x77\x95\xaf\xed\x3c\x29\xb2\xf9\x06\x73\x7c\xca\x09\xa6\xb9\xfd\xa5\xad\x57\x6e\x57\xe4\xab\xc4\xe4\x66\x47\x23\x4a\xad\xcc\xc3\x66\x34\xdf\x79\x52\xc9\x94\xdd\x2e\x29\x5a\x73\x93\xac\xdc\x84\x5b\x2c\x12\x82\xe8\x93\x64\xbf\x4b\xcc\xb3\xa8\x8d\x89\xdd\x24\xf9\x7a\xf6\xf9\x7b\xf8\xa1\xd1\x57\xd5\xce\x11\x80\xbf\x4c\x68\xf5\x1c\x41\x61\x5e\xb7\x5b\x3b\x7b\x4e\xcb\xb7\x37\x5b\x57\xa0\x92\x9d\xa4\xc9\xb6\x4e\xaf\x93\xd9\x99\xfc\x4e\xa8\xdc\xd6\x11\x50\x5c\xd9\xce\xbe\xa6\x3f\xf7\x2d\xfd\x99\x37\x9b\x89\x2b\x97\x49\x91\xef\x93\x1a\x10\x3a\xd7\x8f\x94\xc0\xb4\xc9\xcb\xd2\x95\xb3\xc7\xfc\x33\x21\x00\xcc\xd1\xc4\xec\x89\xdb\x59\x53\xc6\x2d\x20\x6b\x93\x2f\x4b\x80\x91\x72\x13\xc3\x1f\x68\x02\x39\x57\xae\x5c\xcd\xfe\x40\xff\xd1\xe2\x1c\x54\xa3\xce\xa5\x8a\x8b\x3b\x26\x2c\x5c\x5a\xce\xa3\x95\xdd\xbf\xfa\x31\xdb\x27\x37\x71\x89\x4d\x3e\x49\xb2\x0d\x01\x72\x9b\x14\x76\x3d\x7b\x8a\xff\x0d\xa7\x24\x93\x24\x4d\x5d\x53\xd4\xf3\xca\xd6\x75\x5e\x2c\xab\xd9\xb3\xaa\x4e\x76\xb9\x2d\xf2\xc4\xac\x5c\x51\x27\x93\x91\x9c\x49\xeb\x9a\xb0\x9e\xb3\x4b\x5a\xe6\x1b\x23\x5f\x92\x13\xaa\x5c\xee\xdc\x8d\xa5\x6d\x13\x2a\x62\x16\xd5\xfc\xca\xda\x6c\xf6\x15\x0d\xfb\xf6\x07\x93\xac\xea\x26\x59\x17\xee\xf6\xe7\x34\x9f\x6c\x9b\xf5\x9a\xc0\xf6\xa7\x86\xb0\xb3\x9a\x9d\xa7\x7b\x4b\x60\xa0\x3d\x65\xcd\x7e\x93\xd3\xe2\x4f\xf2\xaa\xa2\x3c\x60\xce\x62\x6d\x37\xed\x84\xd6\xad\x48\x69\x4e\xf7\x8b\x66\x4d\xb8\x3f\xf9\xb6\xb2\x49\x99\x5e\xbf\x98\xc8\x2f\x6d\x8a\x6a\xdf\xac\x72\x42\x9c\xdc\x02\x11\x8f\xad\x2a\xd0\x69\x16\x21\x91\x74\x34\xfb\x86\x36\xaa\xab\xf6\x96\x31\xc4\x65\x76\xf6\x95\xcb\xa8\x93\xbc\xa0\x19\xad\xd7\x2f\x26\xfa\xc7\xec\x21\xff\xf2\x6a\xd4\x79\x4d\x30\xf9\xaa\x74\xab\xdc\xe4\x9a\xdc\xde\x14\xd6\x64\xeb\xc4\x6c\x73\x22\x07\xd4\xde\xd2\x99\xa6\x6c\x52\x22\x09\x02\x96\xcc\xa5\x2b\xda\x21\xd8\xeb\x34\x8e\x87\x57\x86\xc0\xf8\x76\x49\x78\xd3\x14\x05\x01\x92\x28\xcc\xb2\x42\x6b\x79\x66\xcd\x03\x2e\x7b\x6a\xb6\x6b\x9b\x54\x40\xad\x24\x33\x1f\x27\xa6\x4e\xca\xa5\xad\x67\x27\xf3\x05\x6d\xc9\xd5\x89\xb9\x2e\xed\xd5\xec\xe4\x5e\x75\xf2\xc9\x17\x0d\x55\x5b\xe7\x85\xad\x3e\x7e\x3f\xf9\xc4\xa4\xb4\xfb\xaf\x08\xce\xad\x59\x58\x42\x36\x8b\xbe\x0c\xa1\x7c\xb1\xb4\x86\x40\x5c\x5f\xa3\xc3\xbc\x30\xf4\x47\x65\xb0\xf7\xdf\x9a\x60\x45\x88\x3e\xcc\xb3\x85\x90\x44\x1e\x0e\x27\x96\xb6\x32\x8f\xdb\x8b\xff\xfe\xe8\xd4\x3c\x75\x55\xbd\x2c\x2d\xff\x4d\xff\x51\xf9\x0f\x09\x09\xcd\x65\xfe\xe0\xb3\xe9\x84\x6a\x0a\x60\x22\xf4\x5a\x24\xfb\xd6\x64\xd4\x65\x7a\xcd\xf9\xd8\x95\x97\xed\xb6\x97\x7e\x4d\x8d\xce\xbe\xa4\xff\x0e\x57\x68\xb8\xb1\xa9\x89\x88\x20\x0c\x1a\x57\xc8\x12\xde\x54\xfb\x57\x3f\x31\xc9\x79\xf5\x3f\x89\xf8\xad\x99\xe8\x3c\x7c\xf2\xe4\xfc\xc1\x67\x66\x4f\x18\x9f\x39\xc6\x95\x8d\x69\xea\xab\xff\x32\x5f\xda\xc2\x96\xc9\x7a\x9e\xe6\xbc\x7c\x3c\xd3\xe9\xa4\xaa\xd6\x44\xc0\x08\x1b\x2e\xcb\x76\x61\x2e\x2e\x1e\xd1\x58\xea\xeb\xd9\xed\x3f\xa7\xb9\xbd\xfd\x85\x08\x50\xf5\xa7\x35\xc0\xa5\x9d\x5e\x5e\x5b\x83\x8d\x62\x50\xca\xb8\xab\x21\x74\x68\x98\x75\xb2\xa0\xb5\x9c\x4e\x6c\x59\xce\x89\xc2\xd6\x2d\x40\xcd\x8d\x1e\x2b\x2b\x8d\x11\xf6\x17\xae\xa6\x85\x34\x5c\x4b\x1a\xc8\x8b\x97\xc9\x3a\xcf\x08\xe0\x1e\x24\xfd\x9a\x48\x32\x99\xa3\x95\x43\x5d\x42\x5f\xb7\xc3\xfa\x13\x09\xa2\xc3\xa1\x32\x27\xd3\x13\xc2\x83\xcc\x9c\xbc\x77\x32\x9d\x14\x6e\x2e\xa4\x03\x44\x3b\xa3\x03\x8f\xb6\xdd\x5c\x0e\x92\x52\x48\xe0\xd7\x72\x94\x10\xee\xe7\x86\x00\x67\x36\xee\xf6\x17\x1c\x80\xed\xed\x0f\xb4\x71\xf7\x00\xf3\xc2\xee\x4d\x4d\x4b\xb5\xef\xa8\x8a\x92\x9f\x78\xc6\x9e\x44\xf5\x56\x55\xcb\x85\x96\xad\x59\xa0\xc5\x2d\x48\xca\x74\x32\xf1\xeb\x73\x80\x5b\x6e\xf9\xb7\xbf\xac\x69\xd3\x01\x53\x27\x38\xca\x23\xd4\x48\xb6\x6b\x5a\x7Line truncated
|
||||
|
||||
func confLocaleLocale_plPlIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4519,12 +4519,12 @@ func confLocaleLocale_plPlIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_pl-PL.ini", size: 46623, mode: os.FileMode(493), modTime: time.Unix(1446027990, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_pl-PL.ini", size: 46574, mode: os.FileMode(493), modTime: time.Unix(1448150144, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_ptBrIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\x4b\x8f\x1c\x47\xb6\xde\xda\x04\xf8\x1f\x72\x78\x41\x53\x02\x9a\x25\x48\xf2\x0b\x82\x4a\x72\xb3\xd9\x7a\x0c\x48\x76\x0f\x9b\xa2\x01\x0b\x42\x29\xba\x32\xba\x3a\x87\x59\x99\xa5\x8c\xcc\x26\x9b\x17\x77\x71\xe1\xad\x7f\x80\xe1\xd5\x15\x66\x71\xa1\x01\xb4\x1a\x78\x33\x4b\xf7\x3f\xf1\x2f\xf1\xf9\xce\x39\xf1\xca\xcc\x6a\x52\x73\xed\x3b\x18\x88\x5d\x19\xef\x13\x27\x4e\x9c\x77\x98\xdd\x6e\x55\x5a\xb7\x5e\x7e\xb7\x2d\x9c\xed\xae\xaa\x9b\x7f\x6e\x8b\xd2\x16\x5f\x57\x7d\x61\x86\xbe\x7d\x78\xd9\xba\x9d\x2d\x4d\xd9\x16\xb6\x30\xdb\x6a\x73\xf3\xf3\x95\xad\x0b\x6a\xd1\x55\x3d\x7d\xdb\x16\x5f\xb7\x77\xef\xdc\xbd\x73\xd9\x6e\xed\xf2\xf4\xe6\xe7\x4d\xd5\x98\xe2\xdb\xa6\x5a\x57\xa6\xbe\x7b\xa7\x34\xee\xf2\xbc\x35\x5d\xb9\x3c\x35\x55\x43\xed\xa8\xe7\x75\xdb\xf4\x5d\x5b\xdb\xbb\x77\xec\x9b\x5d\xdd\x76\x76\x79\xcc\xff\x9a\x8e\x7a\xb1\xf5\x6e\x79\xf8\xc7\xa1\x34\x77\xef\xb8\x6a\xd3\xac\xaa\x66\x79\x4c\xd5\x51\xc6\xbf\xdb\xa1\x5f\x9e\x99\xca\xff\x1c\x76\xcb\x23\x43\x83\x48\x8d\xce\x6e\x2a\xd7\xdb\x6e\xf9\x9c\xff\xe0\x6f\xaf\xed\xb9\xab\x7a\xbb\x3c\xa3\xff\xdc\xbd\x73\x65\x3b\x57\xb5\xcd\xf2\x25\xfd\x7b\xf3\x27\x9a\xf8\xce\x6c\xc2\xb4\xef\xde\xe9\xed\x76\x57\x1b\xaa\xfd\xb4\x2d\x6d\x4d\xc5\xb5\x69\x36\x03\xaa\x7c\x5b\x56\xed\x96\x6a\xac\x3b\x4b\xe5\xab\xc6\xbe\x5e\x1e\x75\x95\xe9\x16\x8b\xc5\xdd\x3b\x03\x01\x6e\xb5\xeb\xda\x8b\xaa\xb6\x2b\xd3\x94\xab\x2d\x56\x75\x6a\x3b\xfa\x50\x10\xe0\x06\x37\xdc\xfc\xdc\x55\x80\x20\x2d\xfe\xa2\xda\x0c\x9d\xb9\xf9\xe7\x9b\xff\x65\x9d\x2c\xc3\x96\xb4\xce\x95\x71\xcb\x97\xed\xfa\xe6\xcf\xc5\xcd\x2f\x00\x28\x3a\x6d\x0c\x01\xf5\x3b\x6d\x4d\xf0\xda\x9a\xaa\x5e\x1e\x3f\xc4\x3f\x98\xba\x73\xaf\x5b\x02\xed\x99\x6d\x2e\x0d\x56\xbf\xea\xaf\x77\x96\x16\x5f\x56\x1b\x5e\xed\xda\xec\xfa\xf5\xa5\x21\x10\xf1\xbf\xe8\xb5\xb3\xbb\x96\xe0\xd1\x76\xd7\x54\x8f\xff\xbc\xf9\x0b\xf7\xdd\x76\x1b\xd3\x54\x6f\x4d\x0f\xf0\x9c\xe8\x0f\x9a\x24\x80\xb4\xad\xba\xae\xed\x96\xc7\x84\x08\xf5\x25\xfd\xa6\xd5\xaf\xd0\xd1\xf2\x59\x7b\xd5\x16\x79\x3f\x28\x23\x2c\xe9\x00\x45\x2a\x36\xc5\x53\xfc\xd0\x8e\x50\x78\xd1\x76\xaf\xa4\xe1\x57\xf4\x17\x10\x62\xda\x01\x4d\x46\x1a\x8f\x27\x62\x1a\xda\x0c\x2e\xfe\xda\x76\xb6\x21\x24\xeb\xd2\x3a\x0c\x51\x53\x6e\x09\x9a\x3b\x43\xe8\x16\xb0\xae\x2d\x0e\xf1\x95\x91\xa2\x6c\x09\x2d\xcc\x7a\xdd\x0e\x4d\xbf\x72\xb6\xef\xab\x66\xe3\x96\x47\xf9\xc6\x14\xa5\x29\xe8\x53\x0f\x3c\xdc\x53\xe5\xee\x9d\xeb\x76\x08\xfb\x4e\xbb\x30\x14\x3b\xde\x72\x2d\x08\xed\xce\x06\xe3\xa6\x1b\xcf\x2b\x75\xab\x0b\x6b\xcb\xe5\x57\xf4\x1f\x40\xe2\x59\xdb\xdf\xfc\x4a\x8b\xa2\xe2\xdd\x50\xd7\x04\xe4\x9f\x06\xeb\x7a\xea\xa2\xad\xe9\x44\xf5\x61\x72\xb6\x38\xa5\xf2\xbb\x77\x2a\xe7\xa8\xc2\xf2\xb4\x6b\xcf\x6b\xc2\x0e\xee\x76\x6d\x9a\x35\x2d\xfd\x88\xff\xc1\x11\xb8\x7b\xe7\x7b\x67\x4d\xb7\xbe\xfc\x01\x8b\xc1\x1f\x84\x9b\xee\xa7\xa1\x72\x8a\xbf\x7b\x91\x02\x38\x98\xe0\x1f\x8f\x16\x06\xa3\x91\xe8\x94\x2c\x8f\x6e\xfe\x42\xf8\xc6\x34\xe0\xfb\xaa\x71\xbd\xa9\x6b\x1a\x47\xff\x5a\x7e\xcb\xff\xfa\xfd\xeb\xab\x9e\x20\x75\xdc\x1b\xc2\x5d\x2c\xa2\x4a\x4a\x8b\x9d\xe9\x4c\x71\xda\x55\x5b\x5b\xd1\x1f\xc7\x6f\xec\x7a\xd0\x66\x65\xbb\x7e\x45\x27\x0c\xc4\x81\xe6\x73\x66\x8b\x2b\x3e\x27\x04\x99\x9b\x9f\x8b\xae\x2d\xe9\xc8\xb5\x45\x4b\x84\x68\x83\x5e\x41\x5d\xb0\xe3\x8f\xb9\xd5\x41\xb1\x6b\xbb\xe2\xc2\x5c\xd1\x7f\x6b\x5b\x99\xa2\x75\xc5\xe7\xa6\xe8\x4d\xb7\xb1\xfd\xf2\xde\xea\x9c\x8e\xf8\xab\x7b\xc5\x65\x67\x2f\x96\xf7\xee\xbb\x7b\x5f\x7c\x3d\x10\xfc\x3f\xff\xc8\x7c\x51\xac\x87\x0a\x74\xcf\xd1\x21\x6c\x7a\x22\x7e\xf4\x1f\x9e\xf5\x96\xc8\x53\x57\xfc\x34\x98\x9a\x76\xa7\xa3\x9d\x25\x40\x16\x0d\xcd\xc6\x14\x3b\xa1\x24\xbf\x03\x4c\x09\xc0\x44\x29\xca\x73\x21\xaf\x3c\x39\xde\xcf\xae\x78\x7a\x7d\xf6\x87\x27\x07\xc5\x69\xeb\xfa\x4d\x67\xf9\x6f\xfa\x0f\xd5\xfe\xb4\x68\x87\xe2\x45\xf5\xf8\x11\x6d\x0a\x35\x14\x70\x4d\x30\xd3\x16\x8f\x68\x6f\x99\x4a\x3f\xc6\xfc\xa4\x2e\x8e\xfe\x8b\x6a\xc7\x4b\x1f\x97\x13\x0d\xef\x97\xdf\xd0\x7f\x26\x5b\x3a\x26\x22\xd4\x13\x13\x9d\x67\x44\xce\xe7\x7a\xa2\x72\xdd\x86\x53\x0f\xd6\x03\xa2\x6f\x96\xc0\xbf\xb5\x84\x79\x95\xdb\xb6\xc5\xb7\xcf\x9e\x9d\x3c\x7e\x44\x80\xd9\xd2\x67\x42\xfc\x3f\xd2\x49\xe3\x4e\xd6\xb4\xc5\x6b\x22\xd0\xb4\x8a\xa1\xbf\xf8\x4f\xab\x8d\x6d\x6c\x67\xea\xd5\xba\x92\xdd\x67\xc0\xd0\xda\x9d\xab\x89\x86\x96\x4c\x87\xdb\xe2\xec\xec\x09\x26\xda\x5f\x12\x4e\xd3\x21\x06\x05\x72\x3f\xd5\x00\xae\x4e\xe5\x84\x3a\xe6\x02\xcc\xd8\x74\x04\xf8\x2b\xfe\xf3\xdc\x4f\x9e\xb7\x71\x06\xc6\xb6\xeb\x56\x44\xf2\xfb\x6b\x6c\x13\x8f\x70\x52\x1c\xc5\xae\x6e\x6f\x5f\x34\x8c\xb5\x34\x4d\x5c\x9c\xc5\x95\x79\x5b\xb5\xda\x67\xd5\x5c\x99\xba\x2a\x69\x03\xc7\xf0\x1c\x75\x99\xf4\x63\xbb\x2d\xf5\x0e\xf4\x4c\xa0\x74\x6f\x71\x8f\x2e\x8d\x7b\x0f\xef\x51\xc7\x4d\xbb\x12\xd2\x86\x1b\xa6\x24\x9c\xa3\x53\xb8\xea\xf4\xa6\x63\xb2\x2d\xd7\x47\x9c\x16\x21\x9e\x39\xaf\x08\x52\x84\xae\x6d\xa1\x55\x5b\x9a\xed\xb6\x58\xe3\xf2\x2a\x86\xad\xe1\xeb\xd8\x60\x46\x26\xa5\x90\x19\x70\x3c\x45\x55\x54\x39\xa4\x1e\x08\x59\x26\x6d\x66\x00\x62\x16\xa0\x0b\x7e\x9b\xe7\xf1\x99\x28\xb9\xa9\x1c\x8Line truncated
|
||||
var _confLocaleLocale_ptBrIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\xdd\x8e\x1c\x47\x96\x18\x7c\xfd\xd5\x53\xa4\xb8\x20\x28\x02\xcd\x12\x24\xed\x67\x1b\x82\x4a\x72\xb3\xd9\x12\x35\x20\xd9\x3d\x6c\x8a\x0b\x58\x20\x4a\x51\x99\xd1\xd5\x39\xcc\xcc\x28\x65\x64\x36\xd9\x5c\xec\xc5\xc2\xb7\x7e\x00\xc3\x57\x2b\xcc\xc5\x42\x03\xe8\x6a\xe0\x1b\x5d\xba\xde\xc4\x4f\xe2\xf3\x17\xbf\x99\xd5\xa4\x66\xed\x1d\x0c\xc4\xae\xc8\xf8\x3d\x71\xe2\xc4\xf9\x0f\xb5\xdb\xad\x2b\x6d\xcb\xd5\xf7\x6d\x61\x75\x7f\x5d\xef\xff\xd5\x14\x95\x2e\xbe\xad\x87\x42\x8d\x83\x79\x70\x65\xec\x4e\x57\xaa\x32\x85\x2e\x54\x5b\x6f\xf7\x3f\x5f\xeb\xa6\x80\x16\x7d\x3d\x40\x59\x5b\x7c\x6b\x16\x8b\x2b\xd3\xea\xd5\xf9\xfe\xe7\x6d\xdd\xa9\xe2\xbb\xae\x2e\x6b\xd5\x2c\x2a\x65\xaf\x36\x46\xf5\xd5\xea\x5c\xd5\x1d\x34\x82\x6e\x4b\xd3\x0d\xbd\x69\xf4\x42\xbf\xdd\x35\xa6\xd7\xab\x53\xfa\x57\xf5\x8b\x2b\xdd\xec\x56\xc7\x7f\x1a\x2b\xb5\xb0\xf5\xb6\x5b\xd7\xdd\xea\x14\xea\xc2\x17\xfa\x69\xc6\x61\x75\xa1\x6a\xf9\x35\xee\x56\x27\x0a\xfa\xa7\xef\xbd\xde\xd6\x76\xd0\xfd\xea\x39\xfd\x81\x45\x6f\xf4\xc6\xd6\x83\x5e\x5d\xc0\x7f\x16\xd7\xba\xb7\xb5\xe9\x56\x2f\xe1\xdf\xfd\x9f\xcd\x62\xa7\xb6\x7e\xb2\x8b\x41\xb7\xbb\x46\x41\xd5\xa7\xa6\xd2\x8d\x59\x34\xaa\xdb\x8e\x58\xe1\xbb\xaa\x36\xad\x5a\x94\xbd\x86\xaf\xeb\x4e\xbf\x59\x9d\xf4\xb5\xea\x97\xcb\xe5\x62\x04\x40\xad\x77\xbd\xb9\xac\x1b\xbd\x56\x5d\xb5\x6e\x71\x25\xe7\xba\x87\x82\x02\x00\x35\xda\x71\xff\x73\x5f\x23\xc4\x60\xbd\x97\xf5\x76\xec\xd5\xfe\x5f\xf7\xff\x53\x5b\x9a\xbd\xae\x60\x71\x6b\x65\x57\x2f\x4d\xb9\xff\x4b\xb1\xff\x65\x41\x3d\x76\x0a\x60\xf8\xbd\x34\x5d\xe8\x56\xd5\xcd\xea\xf4\x01\xfe\x03\x33\xb6\xf6\x8d\x01\x40\x5e\xe8\xee\x4a\xc1\x82\xd7\xc3\xcd\x4e\xc3\x7a\xab\x7a\x8b\x2b\x2c\xd5\x6e\x28\xaf\x14\xc0\x84\xfe\x5d\x40\x8d\x9d\x01\x00\x98\xfe\x06\x2a\xd1\x9f\xfb\xbf\x62\xaf\xa6\xdf\xaa\xae\x7e\xa7\x06\x84\xc7\x99\xfc\x80\xa9\x01\x54\xda\xba\xef\x4d\xbf\x3a\x85\xdd\x6e\xae\xcc\x02\x16\xbc\xc6\x5e\x56\xcf\xcc\xb5\x29\x92\x4e\xf0\x13\x20\x42\x8f\x50\x83\xaf\xaa\x78\x8a\x3f\xb8\x17\xfc\x76\x69\xfa\xd7\xdc\xec\x1b\xf8\x0b\x77\x7d\xd2\x1c\xe6\xc1\x4d\xb3\x39\xa8\x0e\x40\x4f\x5f\xbf\xd5\xbd\xee\x00\x8d\xfa\xb8\x0a\x42\x50\x55\x2d\x40\x6f\xa7\x00\xa1\x3c\x5e\x99\xe2\x18\x4b\x69\xf3\x2b\xd3\x2f\x54\x59\x9a\xb1\x1b\xd6\x56\x0f\x43\xdd\x6d\xed\xea\x24\xdd\x86\xa2\x52\x05\x14\x0d\x80\x6a\x07\x6a\x2c\x6e\xcc\xe8\xf7\x18\xc0\x3e\x16\x3b\xda\x5e\x2e\xf7\xad\x2e\x46\x65\x27\x7b\x8c\x2b\xb4\xeb\x4b\xad\xab\xd5\x37\xf0\x1f\x04\xc0\x33\x33\xec\x7f\x85\xd5\xd8\xc5\x6e\x6c\x1a\x00\xec\x4f\xa3\xb6\x03\xb4\x37\x0d\x9c\x95\xc1\x4f\x4b\x17\xe7\xf0\x7d\x51\x5b\x0b\xdf\x57\xe7\xbd\xd9\x34\x80\x08\xd0\x65\xa9\xba\x12\x16\x7c\x42\xff\x00\x82\x2f\x7e\xb0\x5a\xf5\xe5\xd5\xab\x05\xff\x0b\xd8\x67\x7f\x1a\x6b\xcb\x08\x7a\x68\xfb\x11\xcd\x02\x8a\xd1\x28\x7e\x90\x45\x09\x07\x60\x75\xb2\xff\x2b\xe0\x14\x9c\xe8\x1f\xea\xce\x0e\xaa\x69\x5e\x2d\xe4\x8f\xd5\x77\xf4\xaf\xec\xd4\x50\x0f\x00\x97\xd3\x41\x01\x6a\xe2\xb4\xeb\xe8\x63\xb1\x53\xbd\x2a\xce\xfb\xba\xd5\x35\xfc\x71\xfa\x56\x97\x23\xb7\xaa\x4c\xf9\x1a\x8e\x0e\x1e\x75\x98\xc8\x85\x2e\xae\xe9\x04\x00\x28\xf6\x3f\x17\xbd\xa9\xe0\x2c\x99\xc2\x00\x45\xd9\x62\xa7\x48\x29\x70\x6f\x1f\x51\xab\xa3\x62\x67\xfa\xe2\x52\x5d\xc3\x7f\x1b\x5d\xab\xc2\xd8\xe2\x4b\x55\x0c\xaa\xdf\xea\x61\x75\x67\xbd\x81\x93\xfb\xfa\x4e\x71\xd5\xeb\xcb\xd5\x9d\xbb\xf6\xce\x57\xdf\x8e\x00\xef\x2f\x3f\x51\x5f\x15\xe5\x58\x23\x01\xb3\x70\xc0\xba\x01\xa8\x18\xfc\x87\x26\xdd\x02\xb1\xe9\x8b\x9f\x46\xd5\xc0\x76\xf4\xb0\x8f\x00\xbf\xa2\x83\xd9\xa8\x62\xc7\xe4\xe1\xa3\x05\x6e\x15\x1c\xb1\x75\xb5\x61\x32\x49\x73\xa3\xfd\xeb\x8b\xa7\x37\x17\x7f\x7c\x72\x54\x9c\x1b\x3b\x6c\x7b\x4d\x7f\xc3\x7f\xa0\xf6\xe7\x85\x19\x8b\x17\xf5\xa3\x87\xcb\x05\xb4\x63\x58\x4d\x50\x50\x17\x0f\x61\x37\x89\xd8\x3e\xc2\xd9\x51\x55\x3c\xd6\x2f\xea\x1d\xad\x3b\xfb\x0c\x84\x78\x58\x3d\x86\xff\x64\xdb\x98\x11\x07\xe8\x85\x28\xc9\x33\x20\xc9\x33\xbd\xc0\x67\x01\xff\xb9\x03\xe7\x11\x10\x2c\x0d\x60\x6f\x35\x60\x59\x6d\x5b\x53\x7c\xf7\xec\xd9\xd9\xa3\x87\x00\x90\x16\x8a\x01\xbd\xff\x04\x87\x89\xfa\x28\x61\x67\x4b\x20\xb5\x30\xff\x71\xb8\xfc\x4f\xeb\xad\xee\x74\xaf\x9a\x75\x59\xf3\xa6\x13\x44\x96\x0b\x6b\x1b\xa0\x89\x15\x11\x55\x53\x5c\x5c\x3c\x81\x49\x0e\x57\x80\xbd\x70\x48\x81\xb6\xd8\x9f\x1a\x04\xa9\xcc\xe3\x0c\x7a\xa5\x72\x9c\xad\xea\x01\xdc\xd7\xf4\xe7\xc6\x4d\x9c\xf6\x6e\x0a\x59\xdd\xf7\x6b\x20\xde\xc3\x0d\xee\x0d\xf5\x7f\x56\x9c\x84\x9e\x6e\x6f\x5e\x74\x84\xa8\x30\x47\xbc\xf5\x8a\x6b\xf5\xae\x36\xdc\x65\xdd\x5d\xab\xa6\xae\x60\xd7\x72\x48\x66\x3d\x46\xdd\xe8\xbe\x85\xce\x11\x23\x23\x00\xdd\x59\xde\x81\x0b\xe0\xce\x83\x3b\xcb\x45\x67\xd6\x4c\xb6\xf0\xb2\xa8\x00\xcb\xe0\xc4\xad\x7b\xb9\xad\x88\x16\xf3\x55\x10\x26\x05\xb8\xa6\x36\x35\x80\x09\x10\xd4\x14\x52\xd5\xc0\x5c\xdb\xa2\xc4\x6b\xa8\x18\x5b\x45\x97\xa9\xc2\x09\xa9\x98\xfa\xc5\x90\x71\xc4\x52\x30\xe4\x18\x3a\x00\x1c\x99\x34\x99\x81\x86\x5a\x2e\x16\x6e\x77\xe7\x11\x18\x28\xb4\xaa\x2d\x02\x06\xcf\xc4\x02\x19\x89\x04\x5c\xc7\x3b\xa0\x6d\xb0\xba\x6b\xe3\xbf\xb9\x2d\x3f\x31\x8d\x81\x13\x04\x2b\xeb\xa8\xbLine truncated
|
||||
|
||||
func confLocaleLocale_ptBrIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4539,12 +4539,12 @@ func confLocaleLocale_ptBrIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_pt-BR.ini", size: 47692, mode: os.FileMode(493), modTime: time.Unix(1446027992, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_pt-BR.ini", size: 47612, mode: os.FileMode(493), modTime: time.Unix(1448150152, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_ruRuIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xf9\x8e\x14\x67\x9e\xe0\xff\x25\xd5\x3b\x7c\x66\x84\xb0\xa5\x22\x2d\xb7\xf7\x92\xe5\xc4\x8b\xc1\x6d\xb3\x0b\x98\xa1\xf0\x8e\x56\x96\x95\x8e\xca\x8c\xaa\x8a\x21\x33\x23\x3b\x22\x92\x72\xf5\x68\x24\xa0\xec\xb6\x7b\xf1\x98\x6e\xc6\xbd\xdd\xdb\x63\x43\xd3\xde\x39\xa4\xd6\x68\x93\x82\x82\xa4\xa8\x2a\x24\x3f\x41\xe6\x2b\xcc\x93\xec\xef\xfa\xae\x88\x2f\x32\x13\xbb\x77\xff\x80\xca\x88\xf8\xee\xe3\x77\x1f\xd1\x60\xd0\xea\xc4\x79\xbb\x39\xf9\x6e\xf2\x68\x72\x34\x79\x30\x39\x9c\x8c\xa6\xb7\xd5\xf4\xc6\xe4\xd9\x74\x67\xf2\x18\x5e\x8c\x14\x7c\x79\xc6\xef\xa0\xc0\xf4\xc6\xf4\xe6\x64\x77\xb2\x07\x05\x0f\xe1\xf9\xe1\xe4\x48\xbd\x9b\x14\x27\xa7\xd7\xe1\xd5\x73\x78\xf1\x64\x32\x86\x02\x47\xf0\x3c\x9e\xde\x5e\x51\xd8\x1e\xbc\x1f\x43\xe5\x11\x55\xc1\xd6\xf1\x8f\x9a\xde\x9e\x3c\x99\xde\x9a\xec\x4f\xf6\xd4\xbb\xe9\xf2\xd2\xf2\xd2\x66\xda\x8b\x9b\x93\xbf\x9f\x3c\x83\x92\xbb\x5c\x72\x79\xa9\x13\xe5\x9b\x6b\x69\x94\x75\x9a\x93\x7b\xd4\xc2\x1e\x8c\xe5\x4b\x35\x39\x80\xae\x0e\x6d\x57\xf0\xfb\xe1\x64\xb4\xbc\x14\x7f\x32\xe8\xa6\x19\x34\x73\x17\x46\xfe\x04\xbf\x41\xbb\x71\x77\x80\xb5\x8f\xb0\xd2\xf4\x97\xd3\x2f\x97\x97\xf2\x64\xa3\xdf\x4a\xfa\xcd\xc9\x1d\x78\xfb\x14\x1a\x19\xcb\xbb\x74\x58\xc0\xcb\xe9\xad\xe9\x67\xf0\xe1\x91\xbc\x1c\x42\xf5\x3f\x40\xc7\x0f\x71\x1a\xd3\x9b\xd0\xdf\x68\xfa\x0b\x9c\xde\xf2\x52\x16\x6f\x24\x79\x11\x67\xcd\xc9\x6f\xe1\xe5\x75\xaf\xd0\x18\xfe\x1d\xc1\x4c\x46\xf0\xf4\x25\xbc\x83\xe2\x5b\xf1\x5a\x9e\x14\x31\xf6\xbb\x37\x79\x70\x92\x16\x05\xba\x5f\x5e\xba\x16\x67\x79\x92\xd2\x80\xf6\xa6\xd7\xe1\x3d\xb5\x3e\x88\x36\xa0\xec\x7d\xee\x92\xa6\xfb\x0b\x9c\x64\x11\xf7\x06\xdd\x08\x9b\xf9\x17\x78\xfd\x00\x16\x0c\xd6\x62\x79\xa9\x1b\xf5\x37\x86\x54\xe3\xff\xf0\xca\x2e\x2f\xb5\xb3\x18\xca\xb5\xfa\xf1\x16\x34\x43\x9b\xf3\x88\x87\xd3\x68\x34\x96\x97\x86\x79\x9c\xb5\x06\x59\xba\x9e\x74\xe3\x56\xd4\xef\xb4\x7a\xb4\x74\xf7\x70\xdc\xd3\x4f\x61\x51\x69\xa5\xc7\x0a\x9e\x70\xd3\x47\xf8\x4c\x9b\xbe\xc7\x0b\x13\x77\x60\x0d\x5b\x51\x4e\x2b\xa6\x60\xa6\x47\xd3\x2f\x60\x30\x50\x61\x1f\x0a\xef\xe3\x9e\x62\x17\xfd\x08\xf7\xf5\x77\x93\x03\xdc\x79\x3c\x22\xd8\xcc\x13\xbd\x34\xb4\xa1\x30\xd7\xb8\x17\x25\xdd\xe6\xe4\x5f\x27\xcf\x1a\x54\x6a\xfa\x39\x76\x89\x6b\x90\xe7\x5b\xa9\xec\x3f\xad\xe8\x33\xdc\xc1\x2c\x6e\x15\xdb\x83\x98\xf7\x75\x97\x8f\x01\xcc\x37\x1a\x14\xed\xcd\xa8\x79\x86\xff\xe2\x08\xb2\x78\x90\xc2\x9a\xa7\xd9\x36\x6f\x62\xe9\x8c\x4e\x9e\x2e\x2f\xa5\xd9\x46\xd4\x4f\x7e\x1e\x15\xb4\x03\x77\xe1\xf5\x43\x5e\x6e\x28\x68\x76\xba\x97\x64\x59\x4a\xfb\x0c\x1b\x44\x13\x84\x65\x5f\x5e\x82\xa5\x6d\x61\x17\xcd\xc9\xb7\x34\x90\x5b\x93\xa7\x2a\x74\x17\xb0\x1f\x2c\xdb\x4b\x36\x32\xda\xba\x6f\x65\x01\x60\x4d\xbe\x81\xcf\x0f\xdd\x53\x85\x05\xd7\xd3\xec\xaa\xdb\xe8\xe4\x39\x4d\x7e\x6f\xb2\x3f\xbd\xa9\x70\x68\xc1\x6e\x74\x6d\x98\x92\xd7\x45\xdd\xa4\xa2\x3e\x1c\x18\x2e\xfd\x1d\xf5\x80\x57\xef\x19\xdd\xee\x31\x5c\xcc\x50\x3d\xf8\x08\x73\x89\x3a\x3d\xd8\xfb\x41\xd4\x8f\xbb\xa5\x9b\x39\x82\x33\x76\x40\x37\xd2\xde\x16\x1e\x1d\xec\x66\xd4\x6e\xa7\xc3\x7e\xd1\xca\xe3\xa2\x48\xfa\x1b\x39\x0e\x72\x24\xc5\xe0\x26\xc2\xba\x8e\xb1\x85\x7d\x5c\x60\x80\x3f\x87\x7c\x06\x66\x94\x5e\x5e\xda\x4e\x87\xe6\x14\xe3\xfd\x19\x4d\xbf\x90\xc5\xd2\x67\x58\xca\xd8\x56\xa8\x10\xf6\x74\x58\x6d\x8e\xd6\x2f\x6f\xad\xc7\x31\x1c\xb9\x7f\xc0\x95\xc0\x31\x28\x3a\xf7\xbb\x72\x13\x68\x05\x06\xc3\x6e\x17\xb6\xfe\x67\xc3\x38\x2f\xf2\xe6\x25\x78\x52\x97\xe5\x69\x79\x29\xc9\x73\xf8\xc5\xf0\x85\xc7\x72\x63\x7a\x0b\x1b\x6f\x47\xfd\x36\x2e\xd9\x5d\x68\xe6\x80\xd6\x79\x84\xaf\x3f\xcc\xe3\x28\x6b\x6f\x7e\x84\x73\xc5\x1f\x7c\xae\x11\x6a\xee\xd3\x55\x5d\xe0\x14\xe3\x55\xe3\x6a\xd5\xfb\xf5\xa5\x8c\x48\x2e\x37\xc1\x8c\x3d\xe8\x1f\xfa\x6e\xa7\x1d\x78\xfd\x7b\x06\x77\x30\x90\xa4\x9f\x17\x51\xb7\x0b\x23\x91\x5f\x70\x32\xe4\xfa\xf3\x12\xec\x13\x08\x4a\x0a\x5c\x6d\xff\x0b\xdc\x59\x58\xad\x5b\x70\x6e\x60\x75\x47\x08\x09\x35\xea\x80\xf1\xe2\xad\xd9\x15\x84\x81\x47\x09\x96\x65\x07\xa7\x87\xad\x75\xd2\xf6\x55\x00\x45\x08\xa8\x71\x0e\xbf\x41\xec\x83\x95\x77\x11\xac\xe0\x22\x18\xa8\x01\x67\x62\x0f\xe7\x04\x28\x63\x23\x87\x02\xea\x2c\x55\x3d\x09\xed\x1c\xd1\x4e\xc1\xd6\xe0\x51\xc4\xbb\xb1\xc2\xd0\xe6\x31\x01\xae\x1d\x80\xb3\x37\x78\x27\x79\x3f\x10\xbc\x8c\xa9\xa9\x37\x23\x55\x44\xd9\x46\x5c\x34\x8f\xb5\xd6\x00\x88\x5e\x3d\xa6\x36\xb3\x78\xbd\x79\xec\x78\x7e\xec\xd4\xf4\xef\xb0\x1c\xe3\xbe\x5d\xec\x7b\x7a\xeb\xcd\x57\xa3\x53\x2b\x7a\x4a\x7b\x93\x47\x8a\xba\x3d\x10\xa8\xa7\xa8\xe5\xa3\x93\x38\x03\x58\xe7\x23\xba\xab\xb2\xd5\x63\x04\xbd\x2f\xe1\x6e\xfe\x6c\x08\x68\xa0\xd5\x59\x63\xe4\x4b\x93\xa1\x63\x08\x58\x81\xa7\xa8\x2e\x6c\xaf\xfe\xe5\xf9\x15\x75\x29\xcd\x8b\x8d\x2c\xa6\xdf\xf0\x1f\xd4\x7a\x1d\x5b\xc4\xe5\xb9\x92\x9c\x7d\x1b\x8e\x06\x34\x22\xbb\x11\xb8\x4a\x80\xbe\x11\x17\xe0\x3e\x10\xfa\x45\xcc\xc6\x55\x08\x78\xfe\x11\x9a\x7a\x5e\x57\x6a\x13\xba\x6e\x4e\xfe\x91\xcf\xfc\xdc\xf3\x55\x03\xa7\xa1\x2b\x0f\xfe\xd7\x0e\x48\x6f\xfe\xd7\x74\x60\x68\xf6\xd5\xbd\x7f\x4a\x1b\x86\x3b\x02\xc3\xa6\x93\x87\x27\x19\xd0\xa2\x3a\xd7\xef\xaLine truncated
|
||||
var _confLocaleLocale_ruRuIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\xfb\x8e\x1b\xc7\x99\x28\xfe\x7f\x3f\x45\x4b\x81\x60\x1b\x90\x68\xd8\xde\xdf\xef\x1c\x04\x1e\xfb\x38\x56\x12\xe7\x20\x71\xbc\x91\x82\xfc\x61\x08\x74\x0f\xd9\x33\xd3\x2b\x0e\x9b\x61\x37\x25\xcb\x8b\x05\xa4\x99\xf8\x92\x23\x47\x8a\xbd\xce\x89\xe1\xd8\x56\x14\x6f\x36\x0b\x04\xc1\xa1\x46\x43\x89\x73\xa3\x00\x3f\x41\xf3\x15\xf6\x49\x4e\x7d\x97\xaa\xfa\xaa\xba\x9a\xe4\xd8\xd9\x83\xc5\xc6\x9a\x66\xdd\xeb\xab\xef\x7e\x49\x06\x83\x76\x37\x2d\x3a\x6b\xd5\x57\xd5\x7e\x35\xab\x1e\x54\x27\xd5\x78\x7e\x37\x9e\xdf\xaa\x8e\xe6\xbb\xd5\x23\xf5\x61\x1c\xab\x5f\x8e\xe8\x9b\x6a\x30\xbf\x35\xdf\xa9\xf6\xaa\x89\x6a\x78\xa2\xfe\x7e\x58\xcd\xe2\x1f\x66\xe5\x85\xf9\x4d\xf5\xe9\x89\xfa\xf0\xb8\x9a\xaa\x06\x33\xf5\xf7\x74\x7e\xf7\x7c\x0c\xe3\xa9\xef\x53\xd5\x79\x8c\x5d\x60\x74\xf8\x4f\x3c\xbf\x5b\x3d\x9e\xdf\xae\x0e\xab\x49\xfc\xc3\x3c\x8a\xb6\xf2\xed\x74\xad\xfa\xd7\xea\x48\xb5\xdb\xa3\x76\x51\x37\x29\xb6\xd6\xf3\x64\xd8\x5d\xab\xee\x61\xf7\x89\x5a\xc8\x87\x71\x75\xac\xe6\x39\xb1\xf3\xa8\x7f\x3f\xac\xc6\x51\xfa\xf6\xa0\x97\x0f\xd5\x20\x5f\xaa\x55\x3f\x86\x9f\xa2\xad\xb4\x37\x80\xbe\x33\xe8\x32\xff\xf5\xfc\xc3\xa8\xc8\x36\xfb\xed\xac\xbf\x56\x7d\xac\x3e\x1e\xa8\x11\xa6\xf4\x29\x1f\x95\xea\xdb\xfc\xf6\xfc\x5d\xf5\x7d\x9f\xbe\x8d\x54\xdf\x3f\xaa\x39\x1f\xc2\xf2\xe7\x3b\x6a\xaa\xf1\xfc\x3d\xd8\x56\x34\x4c\x37\xb3\xa2\x4c\x87\x6b\xd5\xef\xd5\xb7\x9b\x4e\x9b\xa9\xfa\xff\x99\xda\xc2\x58\xfd\xf5\xa1\xfa\x76\x37\xba\x9e\xae\x17\x59\x99\xc2\x9c\x93\xea\xc1\x05\x3c\x0a\x35\x75\x74\x2d\x1d\x16\x59\x8e\x6b\x99\xcc\x6f\xaa\xcf\x30\xf4\x20\xd9\x54\x2d\xef\xd3\x74\xb8\xcb\xf7\xd4\xde\xca\x74\x7b\xd0\x4b\x60\x8c\xff\x50\x5f\x1f\xa8\x53\x52\x27\x10\xf5\x92\xfe\xe6\x08\xdb\xff\x1f\x3a\xcb\xa8\x33\x4c\x55\xab\x76\x3f\xbd\xae\xc6\xc0\xdb\xd8\xa7\x85\xb4\x5a\xad\x68\x54\xa4\xc3\xf6\x60\x98\x6f\x64\xbd\xb4\x9d\xf4\xbb\xed\x6d\x3c\xae\x7b\xb0\xe0\xf9\xaf\xd4\x39\xe2\xe1\x4e\x63\xf5\x17\x5c\xf2\x18\xfe\xc6\x4b\x9e\xe0\x79\xa4\x5d\x75\x70\xed\xa4\xc0\x73\x8a\xd5\x0e\x67\xf3\x0f\xd4\x42\x54\xfb\x43\xd5\xf6\x30\xc2\xf1\xfb\x09\x5c\xe3\xa7\xd5\x31\x5c\x33\xc0\x03\x8c\xf1\x58\x1f\x08\x5e\xe0\xdd\x28\xdd\x4e\xb2\xde\x5a\xf5\xb7\xea\xa8\x85\x8d\xe6\xef\xc3\x74\x6a\xeb\x45\x71\x3d\xe7\xdb\xc6\x53\x54\x9d\xd5\x59\xb7\xcb\x1b\x83\x94\xae\x71\x8f\xee\x3c\xea\x24\x83\xb2\xb3\x95\xac\xbd\x4a\xff\x8d\x54\xab\x41\xae\x0e\x39\x1f\xde\xa0\x3b\xf3\x40\xb1\x3a\x88\xf2\xe1\x66\xd2\xcf\xde\x49\x4a\x3c\xf2\x2f\xd5\xd7\x87\x74\xc0\xaa\x9d\xbe\xd7\xed\x6c\x38\xcc\xf1\x56\xd5\x85\xe0\xb6\xd4\x41\x47\xea\x34\xdb\x30\xfe\x5a\xf5\x05\x2e\xe1\x76\x75\x10\x87\xe0\x5d\x4d\x02\x4d\xb7\xb3\xcd\x21\x5e\xd5\x17\xbc\x6d\x75\x12\x9f\xab\x5f\x1f\x0a\x00\x82\x76\x1b\xf9\xf0\xaa\x1c\xb2\x7a\x82\x7b\x9e\x54\x87\xf3\x9d\x18\x96\x15\x9c\x84\x3b\xab\xdd\x38\x13\x34\xec\x27\xe9\x2b\xf0\xa0\xc6\x5f\xe1\xf8\xf0\xb8\x8e\xf0\xf5\x4e\xd5\xc3\x0b\x75\x53\x3f\x1e\x44\x49\x77\x5b\xdd\xf5\x20\xe9\xa7\x3d\xef\xed\x8d\x15\x44\x1d\xe3\x9b\xb3\x8f\x82\x96\x36\x8e\x92\x4e\x27\x1f\xf5\xcb\x76\x91\x96\x65\xd6\xdf\x2c\x60\x85\x63\x6e\xa5\x5e\x9b\x3a\xd0\x29\x0c\x70\x08\x27\xab\xb0\xcb\x09\xde\xfa\x82\xc6\xd1\x8d\x7c\x64\x20\x16\x9e\xc9\x78\xfe\x01\x1f\x93\x86\x57\x6a\x62\xc7\xc0\x36\x30\xcd\x49\x6d\x30\x38\xb7\xa2\xbd\x91\xa6\x0a\xc0\xfe\x00\x47\x00\xd3\xc7\x08\xe0\x7b\x0c\xf2\xb0\xf5\xc1\xa8\xd7\x53\xd7\xfd\xcb\x51\x5a\x94\xc5\xda\x1b\xea\xaf\xf8\x67\xfc\x57\x94\x15\x85\xfa\x07\x21\x0f\x5a\xc6\xad\xf9\xed\x48\x41\x63\xbf\x03\x07\xf5\xa5\x1a\xe2\x18\x0f\x77\x1c\x45\x6f\x16\x69\x32\xec\x6c\x5d\x89\xe8\xbf\x04\xbf\x80\x07\x0f\xe1\x2d\x2e\x87\x57\x78\x4d\xd4\xa9\xfe\x84\x3e\xa4\x95\xf0\xdb\x45\x7c\x30\x51\x33\x8f\xa3\x4e\xde\x55\x5f\x3f\x43\x1c\x16\xbd\x99\xf5\x8b\x32\xe9\xf5\xae\x44\xfc\x0f\x05\x04\xfc\xb2\x69\xd3\x87\x80\x59\xb2\x12\xce\xd6\xfd\x41\xbd\x48\x75\x3c\xb7\x15\x84\xa8\xc3\x1c\x03\x72\xd3\x44\x40\x2d\x14\x9e\xc6\x1e\xa3\x7e\x00\x1a\x75\x12\xbb\xb0\x2d\x35\x58\x37\xef\x5c\x55\x38\x06\xd0\x2e\x2c\xfe\x77\x40\x46\xa0\xef\x1e\x20\x0c\xd8\xbb\xc1\x08\xea\xfa\x27\xb0\x19\x85\xfb\x37\x0b\xd5\x20\xbe\x88\x5d\x2f\xa8\x61\x66\x78\x33\xea\x2a\x00\xe8\xe0\x09\x9c\x27\x4c\xf2\x08\x31\xd2\xae\x42\x9d\xb7\xe8\xe6\xe8\x06\x00\x77\x4c\x71\xa8\x17\x93\xb8\x4c\x86\x9b\x69\xb9\x76\xb6\xbd\xae\x70\xe3\xd5\xb3\xf1\xd6\x30\xdd\x58\x3b\x7b\xae\x38\xfb\xd2\xfc\x37\xd0\x8e\x88\xd8\x1e\xcc\x3d\xbf\xfd\xe2\xb3\xc9\x4b\xe7\xf5\x8e\x26\xd5\x7e\x8c\xd3\x1e\x33\x3e\x8b\x71\xe4\xd9\x05\xd8\x81\x3a\xe1\x19\x3e\x49\xbe\xde\x29\xa0\xd4\x33\x11\x40\x89\x42\xec\xed\xee\x3a\x11\x51\xdc\x0b\xc2\x9c\xc2\xf3\xb4\xc3\xf8\x27\x37\x2e\xfd\xe3\x8f\xcf\xc7\x6f\xe4\x45\xb9\x39\x4c\xf1\xdf\xea\x7f\x54\xaf\x17\x60\x40\x38\x9d\xcb\xd9\xc5\xef\xb5\x22\x35\x06\x5f\x45\xe0\xcd\x28\x2a\x0c\x08\x1e\x2e\x01\xa9\x28\xd0\x29\xec\x81\x88\xf1\x4f\x6a\xa0\x27\x0d\x8d\xb6\xd4\xbc\x6b\xd5\x9f\x09\xbe\x97\xc0\x54\x18\xff\xaa\x69\x1c\xa4\xde\xb4\x16\x7d\xe9\x9f\x20\x9c\xe0\xb6\xeb\x77\x7e\x80\x17\x05\x37\xa1\x56\x8c\x00\x07\xb0\xab\x68\x5c\xfc\xa3\x7e\x3f\xbf\xf8\x3d\x75\x3f\x31\x82\xcLine truncated
|
||||
|
||||
func confLocaleLocale_ruRuIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4559,12 +4559,12 @@ func confLocaleLocale_ruRuIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_ru-RU.ini", size: 61542, mode: os.FileMode(493), modTime: time.Unix(1446027992, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_ru-RU.ini", size: 69543, mode: os.FileMode(493), modTime: time.Unix(1448150146, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_zhCnIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\x69\x73\x1b\x47\x96\xe0\x77\x45\xe8\x3f\x54\x68\x42\xd1\x5f\xd6\x74\xd8\xde\x2b\x36\x0c\xef\x76\xdb\x33\xdd\xbd\x61\xbb\xbd\x2d\x3b\xe6\x83\xc3\x01\x83\x40\x91\xc4\x08\x44\xc1\x28\x40\x32\x7b\x62\x22\x48\x49\x24\x41\xf1\x96\x28\x51\x94\x48\x51\xa4\x49\x91\xa2\xc4\x43\xd6\x45\x12\x3c\xfe\xcb\x1a\x55\x00\x3e\xe9\x2f\xec\xbb\x32\x2b\xeb\x00\x65\xf7\xce\xee\x17\x89\xa8\x7c\x79\xbd\x7c\xf9\xf2\x5d\xf9\x32\x53\x2a\xa5\x73\xb6\x9b\x4d\x79\xcb\x87\x8d\xc3\x29\xeb\x8f\x8e\xd5\xda\xdd\x6e\x6d\x0e\x36\xef\xdf\x68\x8d\x6e\x79\x37\x9f\x58\x7f\xcc\x57\x2c\x7f\x71\xd2\xbb\xb9\x72\xfe\xdc\xf9\x73\x7d\x4e\xbf\x9d\x6a\x3f\xbe\xdb\x5e\x79\x75\xfe\x5c\x2e\xe3\xf6\x75\x3b\x99\x72\x2e\xe5\x4f\x6d\x78\xb5\xd7\xed\xa5\x55\x7f\xe9\xf4\xfc\x39\xfb\xc7\x52\xc1\x29\xdb\xf0\x75\xb5\xf9\x72\x15\x2a\xd9\x85\x52\xca\xdb\xdf\x81\xe6\xce\x9f\x73\xf3\xbd\xc5\x74\xbe\x98\x6a\x2e\xd4\xbd\xe3\x3b\xf2\xdb\xa9\x56\x52\xed\xc1\x41\x6f\xf4\x50\x3e\x54\x4b\x29\xff\xc5\xa6\x37\x32\x71\xfe\x5c\xd9\xee\xcd\xbb\x15\xbb\xac\x3f\x5c\xb5\xbb\xdd\x7c\xc5\x4e\x79\x3b\xf7\xfc\xbb\x07\xcd\xe3\xd9\xe6\xd3\x85\xf3\xe7\xae\xd8\x65\x37\xef\x14\x53\xde\xf1\x6d\x6f\x6c\xb2\x39\x56\xf3\x17\x9f\x9d\x3f\x57\xca\xf4\xc2\x78\x57\x5e\xc1\xd0\xce\x9f\xab\xd8\xfd\xa5\x42\x06\x6a\xfa\x9b\x2b\x34\xd0\x42\xa6\xd8\x5b\x45\x08\x9e\x74\x7b\x70\xac\xbd\x72\x70\xfe\x5c\xb6\x6c\x03\x54\xba\x68\x5f\x4d\x79\xb5\x07\x5e\xfd\xb0\xab\xab\xeb\xfc\xb9\xaa\x6b\x97\xd3\xa5\xb2\xd3\x93\x2f\xd8\xe9\x4c\x31\x97\xee\xc7\x39\x36\xe7\x36\xfd\xda\x9b\xc6\xe9\x8a\x3f\xb4\xeb\x4d\xdf\xf4\x1f\xbc\xf4\xd6\xee\xf3\x24\xec\x1c\xcc\x33\x9d\x71\x53\xde\x9b\x9f\x79\xb6\x0c\x8c\x78\xc4\xc6\x8a\x99\x7e\x55\xdf\x9b\x99\x04\xb4\xf5\x67\xf2\x85\x54\xfb\xda\x4e\x73\xe7\x39\x8e\xdc\x75\xaf\x3a\x80\x5b\x6f\x77\xa4\xf9\x68\x08\xf1\x90\xae\x0c\x94\xa0\xc6\xca\x4e\x6b\x67\x4d\x7d\xcd\x66\x4a\x95\x6c\x5f\x26\xd5\xde\x9a\x68\xed\x0e\xd1\x27\x04\x2d\x39\x80\x22\xa7\x3c\x90\x6a\xd4\x6f\x7b\x87\xb7\xcf\x9f\x73\xca\xbd\x99\x62\xfe\x6f\x99\x0a\xe2\xa8\x59\xbf\xd1\xac\x8f\x9e\x3f\xd7\x9f\x2f\x97\x9d\x72\xaa\x7d\x67\xd1\xbb\x3e\x7d\xfe\x1c\x4c\x38\x8d\x55\x65\xd6\xfe\xdd\x3d\x20\x03\xd5\x00\x16\xf6\xe7\x7b\xcb\x88\xbf\xd6\xe9\x50\x73\xa3\xee\xad\xdd\x6d\x5f\xdf\x34\xcb\x7b\x9c\xf2\xe5\x50\x65\xff\xe5\x49\x73\x6e\xd9\x04\x81\x71\x84\x20\xf4\x50\x32\x45\x58\x08\x2a\x6e\xee\xac\x34\x67\x46\xfc\xda\xac\x51\x9c\xc9\xf5\x03\x2e\x4b\x99\xa2\x5d\x90\x72\x45\x6c\x99\x6c\xd6\xa9\x16\x2b\x69\xd7\xae\x54\xf2\xc5\x5e\xc0\xf6\xfe\x0c\x60\xb4\xb5\x73\xd2\x3c\xde\x81\x85\x48\xfe\x3c\xe0\x54\xf5\x62\xa6\x1a\xfb\x5b\x8d\xc3\x43\x5e\x43\x29\xd2\xd5\x78\x7d\x54\x35\x9a\x83\x9b\xee\xb1\x6d\xa0\xf9\xc5\x41\x98\x82\xff\xb2\xee\xdd\xdc\x84\xe5\xaa\x16\x0a\x80\xbc\x1f\xaa\xb6\x5b\x81\xce\x66\x6a\xde\xc1\xeb\xd6\xee\x1b\xff\xf9\xb5\xf3\xe7\xf2\xae\x0b\x9f\x81\x0c\xd6\xbd\xc9\x3b\x3c\x7a\x6c\x2a\x9b\x29\x66\x61\x3a\xde\xf4\x5d\xff\x75\x0d\x3f\x7c\xeb\xda\x99\x72\xb6\xef\x3b\x1c\x35\xfe\x91\xf2\x67\x16\x61\x03\x11\xf5\x25\x2c\x29\xd2\x50\x4a\x91\x14\xf5\x21\x5d\x40\xd3\x4e\x0e\xa6\x55\xff\x49\xe8\xe1\xdb\x7c\xd1\xad\x64\x0a\x05\x68\x59\xfe\x82\xcd\x33\xd6\xfa\x69\x58\xef\x8c\x7c\xa5\x40\xfb\xda\x7f\xb6\xd2\x3a\x9d\x69\xad\x4c\x70\x79\x73\x73\xdc\x3b\x04\xca\xc8\x39\xd9\xcb\x40\xfd\xb8\x91\xa1\x4f\xef\xf1\x35\xff\xe1\xa2\x7f\x6d\xd3\xdf\xfe\xc9\x5b\xdc\x6c\x1c\x9f\xc2\x28\xac\xcf\x08\xc6\xf2\x76\x0e\xbc\x85\x4d\x6e\x04\xf8\x49\xaf\xfb\xf6\x08\x28\xf3\x0d\xf0\x0f\xef\x74\xd8\x1b\xae\x35\xea\x73\xcd\xfa\x48\xfb\xde\x70\x6b\xb7\x6e\x7d\x9c\xb1\x2a\x99\x72\xaf\x5d\x49\x5d\x48\x77\xc3\x6e\xbc\x7c\xc1\xea\x2b\xdb\x3d\xa9\x0b\x17\xdd\x0b\x9f\xf0\xfe\xf6\xef\x8e\xfa\x2b\x3f\x7d\xfc\x7e\xe6\x13\xcb\x9b\x99\xf2\x46\x26\xbd\xdd\x03\xd8\xda\x3c\xf2\xd6\xe9\x03\x1c\xeb\xca\x53\x6f\x64\xe1\x97\xc1\x6b\x88\xa6\x1f\xaa\xc0\x1b\xd2\xb9\x6e\xe6\x6b\x38\x00\xab\xf5\x78\x08\xd6\x81\x67\x64\x7d\x31\x70\xe9\x7f\x7d\xfe\xcb\xe0\xd0\x57\x8e\x5b\xe9\x2d\xdb\xfc\x03\xfe\x85\x5a\x1f\x59\x7e\xed\xae\xf5\x75\xfe\xb3\x3f\x50\x5b\xd0\x06\xe3\xc5\xbf\xb3\xe7\x4f\xee\x00\xce\x15\x19\x60\x09\x6e\x45\x5d\xd0\x7c\x5e\xf7\x1e\x8e\x23\x7b\x74\x2b\xc1\xd7\xc6\x7e\xdd\x5f\x3c\x94\x85\x0a\x60\x65\xc5\xf4\x06\x8f\x94\xa8\x9d\x0d\x9d\x10\x87\xd0\xc5\xc0\x24\x9a\x1b\x7b\x54\x10\x5d\x08\x59\x02\x9a\x1b\xe3\x5b\xbe\xfc\xf9\xcb\x2f\xff\xf2\xd9\x1f\x2c\xef\xe8\x8e\x7f\x7b\xaa\x51\x5f\x07\x16\x65\x55\x2b\x3d\xff\x35\xdd\x6b\x17\xed\x72\xa6\x90\xce\xe6\x2d\x6f\x7b\xbe\xf9\xec\x71\xfb\xc1\x08\xcd\xda\x75\x0b\xc0\xd9\x80\x7c\x2e\x5d\xfa\xdc\x02\x46\xe9\x1d\x4d\xe3\x58\x2b\x7d\xc1\x40\x60\x49\x1a\xf5\xd7\xad\x37\xbb\xde\xc9\x0d\xa8\xf0\x43\x01\x31\x2e\x43\x8a\x62\xd2\x42\x2e\xa0\xf1\x47\x55\xa8\x1b\xbb\x5c\x4e\x03\x3b\xae\x0c\xe0\x52\x51\xf3\xbf\xa2\x66\x63\x7f\xb2\x75\xfd\xb8\xb1\x7f\xd8\x7c\x72\xa8\x5b\xc9\x17\xaf\x64\x0a\xf9\x1c\xac\x95\xc2\x18\xd5\x8e\xa0\x0d\xaa\x7a\xc3\x43\xad\x9d\x7d\x6f\x62\xd8\x9b\x79\xca\x73\xb6\x2e\x74\x5d\xa0\xfe\x2e\xbc\x77\xc1\xa2\x06\x8b\x4e\x9a\x59\x0d\x32\xf8\x5c\xde\xcd\x74\x03\xb3\xeLine truncated
|
||||
var _confLocaleLocale_zhCnIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\x59\x73\x1b\x47\xb6\x20\xfc\x8e\x5f\x51\xa1\x0e\x45\xbf\x7c\x96\xc3\xf6\xfd\x66\x26\x26\x0c\xcf\x74\xdb\x7d\xbb\x7b\xc2\x76\x7b\x5a\xee\xb8\x0f\x0e\x05\x0c\x02\x45\x12\x57\x20\x0a\x8d\x02\x24\xb3\x6f\xdc\x08\x52\x12\x49\x50\xdc\x25\x6a\x27\x45\x51\x16\x45\x5a\x12\x17\xd9\xb2\x44\x12\x5c\xfe\xcb\x34\xaa\x00\x3c\xf9\x2f\xcc\xd9\x32\x2b\xb3\xaa\x48\xd9\x3d\x77\xc6\x0f\x16\x51\x79\x72\x3b\x99\x79\xf6\x3c\x99\xaf\x56\x73\x45\xd7\x2f\x64\x83\x95\xfd\xf6\xfe\xac\xf3\x7b\xcf\xe9\x6e\x6f\x76\x37\x46\x3a\xf7\xaf\x75\x27\x9e\x05\xd7\xbf\x73\x7e\x5f\xaa\x3b\xe1\xd2\x4c\x70\x7d\x35\x93\x19\xf4\x86\xdc\x6c\xef\xe9\xed\xde\xea\x8f\x99\x62\xde\x1f\xec\xf3\xf2\xb5\x62\x36\x9c\x5d\x0f\x9a\xaf\x7b\xcb\x8f\xc3\xe5\xe3\x8c\xfb\x4d\xb5\xec\xd5\x5c\xf8\xf8\xb8\xf3\xea\x71\x66\xd0\x2d\x57\xb3\xc1\xee\x16\xb4\x94\xf1\x4b\x03\x95\x5c\xa9\x92\xed\xdc\x6b\x05\x87\xb7\xf8\xa7\xd7\xa8\x67\x7b\x23\x23\xc1\xc4\x3e\xff\x6e\x54\xb3\xe1\x0f\x1b\xc1\xf8\x74\xa6\xe6\x0e\x94\xfc\xba\x5b\x53\xbf\x2f\xbb\x7d\x7e\xa9\xee\x66\x83\xad\xbb\xe1\xed\xbd\xce\xe1\x42\xe7\xf9\xbd\xcc\x25\xb7\xe6\x97\xbc\x4a\x36\x38\xbc\x19\x4c\xce\x74\x26\x9b\xe1\xd2\x8b\x4c\x35\x3f\x00\xa3\x5c\xfd\x11\x46\x94\xa9\xbb\x43\xd5\x72\x1e\xaa\x85\x1b\xab\x38\xbc\x72\xbe\x32\xd0\xc0\x72\x9e\x66\x6f\x64\xb2\xb7\xba\x97\x29\xd4\x5c\x80\xc9\x55\xdc\xcb\xd9\xa0\xf9\x20\x68\xed\x9f\x3b\x77\x2e\xd3\xf0\xdd\x5a\xae\x5a\xf3\xfa\x4b\x65\x37\x97\xaf\x14\x73\x43\x38\xaf\xce\xe2\x46\xd8\x7c\xd3\x3e\x5e\x0d\x47\xb7\x83\xb9\xeb\xe1\x83\x57\xc1\x93\xfb\x34\x76\xb7\x08\x93\xcb\xe5\xfd\x6c\xf0\xe6\x7b\x9e\x22\xc3\x66\xa8\xa5\x4a\x7e\x48\x55\x0e\xe6\x67\x32\xee\x50\xbe\x54\xce\xf6\xae\x6c\x75\xb6\x5e\xc2\x80\x7d\xff\xb2\x07\x98\x0c\xb6\xc7\x3b\x8f\x46\x61\xea\xb9\xfa\x70\x15\xc0\x57\xb7\xba\x5b\x4f\xe4\x63\x21\x5f\xad\x17\x06\xf3\xd9\xde\xb3\xe9\xee\xf6\x28\x7e\x01\xb8\xaa\x07\x38\xf1\x6a\xc3\xd9\x76\xeb\x66\xb0\x7f\x33\xe3\xd5\x06\xf2\x95\xd2\xdf\xf2\x75\x44\x4a\xa7\x75\xad\xd3\x9a\xc8\x0c\x95\x6a\x35\xaf\x96\xed\xdd\x5a\x0a\xae\xce\x65\x60\x8e\x39\xac\x27\x13\x0d\x6f\xef\xc0\x52\x4b\x6d\x2c\x1b\x2a\x0d\xd4\x10\x5f\xdd\xe3\xd1\xce\x7a\x2b\x78\x72\xbb\x77\x75\xc3\x28\xee\xf7\x6a\x17\xad\xaa\xe1\xab\xa3\xce\xe2\x8a\x01\x01\x43\xb0\x00\xd4\x28\xf2\x15\x40\x3b\x95\x76\xb6\x56\x3b\xf3\xe3\x61\x73\x21\x2a\xcd\x17\x87\x00\x77\xd5\x7c\xc5\x2d\x4b\xb1\x6c\xa7\x7c\xa1\xe0\x35\x2a\xf5\x9c\xef\xd6\xeb\xa5\xca\x00\x20\x77\x77\x1e\x50\xd8\xdd\x3a\xea\x1c\x6e\x65\xd2\xbf\x0e\x7b\x0d\xbd\x70\xd9\xf6\xee\xb3\xf6\xfe\x3e\xaf\x17\x97\xe8\x4a\xbc\x1a\x52\x09\xc7\xee\xe7\xfa\x5d\x17\xf6\xf3\xd2\x08\x0c\x3d\x7c\xd5\x0a\xae\x6f\x64\xaa\x8d\x72\x19\x10\xf6\xd7\x86\xeb\xd7\xa1\x9f\xf9\x66\xb0\xf7\xba\xbb\xfd\x26\x7c\x79\x25\x53\xf2\x7d\xf8\x0a\xcb\xbd\x16\xcc\xdc\xe2\x51\x67\x60\x99\x2a\x05\x98\x44\x30\x77\x3b\x7c\xdd\xcc\x64\xbe\xf2\xdd\x7c\xad\x30\x78\x21\xc3\xff\x66\xc3\xf9\x25\x38\x16\xb8\xbd\x92\x8b\x87\xdb\x44\x06\xc5\x6d\x4b\xd3\x99\x82\x57\x84\x89\xb4\xbe\xa5\x45\xff\xaa\x54\xf1\xeb\xf9\x72\xf9\x42\x46\xfe\x80\x13\x31\xd9\xfd\x76\x4c\x6d\xf8\x52\xbd\x4c\x67\x34\x7c\xb1\xda\x3d\x9e\xef\xae\x4e\x73\x71\x67\x63\x2a\xd8\x9f\xcb\x14\xbd\xc2\x45\xd8\xd6\x78\x2c\xa1\xb3\xe0\xe9\x95\xf0\xe1\x52\x78\x65\x23\xdc\xfc\x36\x58\xda\x68\x1f\x1e\x43\xf7\xce\x27\x04\xe3\x04\x5b\x7b\xc1\xbd\x0d\x6e\x03\x08\xc3\x80\xff\xd3\x01\xec\xbc\x37\x40\x08\x82\xe3\xb1\x60\xac\xd9\x6e\x2d\x76\x5a\xe3\xbd\xbb\x63\xdd\xed\x96\xf3\x61\xde\xa9\xe7\x6b\x03\x6e\x3d\x7b\x26\xd7\x07\x87\xec\xe2\x19\x67\xb0\xe6\xf6\x67\xcf\x9c\xf5\xcf\x7c\xc4\x47\x36\xbc\x3d\x11\xae\x7e\xfb\xe1\xbb\xf9\x8f\x9c\x60\x7e\x36\x18\x9f\x09\xb6\xf7\xe0\xb8\xf2\xb8\xbb\xc7\x0f\x70\xa8\xab\xcf\x83\xf1\x7b\x7f\x1f\xb9\x92\x41\x8c\xc3\x69\xcf\x15\xfb\x98\x3e\x61\xff\x4e\xf7\xe9\x28\xe0\x9d\xe7\xe3\x7c\x36\x7c\xfe\x7f\x7e\xfa\xf7\x91\xd1\x2f\x3c\xbf\x3e\x50\x73\xf9\x07\xfc\x1f\x6a\x7d\xe0\x84\xcd\xdb\xce\x97\xa5\x4f\x7e\x8b\x4d\x41\x13\x8c\x94\xf0\xd6\x4e\x38\xb3\x05\x98\x96\x25\xc7\x02\x3c\x66\xfa\x7b\xe7\x65\x2b\x78\x38\x05\x54\xce\xaf\x47\x1f\xdb\xbb\xad\x70\x69\x9f\x17\x27\x82\xe4\x55\xd2\x07\x37\x56\x20\x47\x16\x3a\xa0\x63\xaf\x4b\xe1\xe4\x77\xd6\x77\xf0\x7b\x7c\x01\x04\xf5\x34\x29\xc6\xb3\x7c\xf9\xe3\xe7\x9f\xff\xe9\x93\xdf\x3a\xc1\xc1\xad\xf0\xe6\x6c\xbb\xb5\x06\x34\xc7\x69\xd4\xfb\xff\x4b\x6e\xc0\xad\xb8\xb5\x7c\x39\x57\x28\x39\xc1\xe6\x9d\xce\x8b\xa7\xbd\x07\xe3\x38\x5d\xdf\x2f\x03\xa5\x82\x0d\x73\xfe\xfc\xa7\x0e\x50\xbd\xe0\x60\x0e\xc6\x59\x1f\x8c\x46\x01\x0b\xd1\x6e\xbd\xee\xbe\xd9\x0e\x8e\xae\x65\xfc\xbf\x96\x11\xcf\x32\x9e\x38\xfe\x1c\x3c\xe1\x1a\x6d\x54\x03\xfb\x70\x6b\xb5\x1c\x10\xd6\xfa\x30\xae\x0f\x35\xfe\x33\x2a\xb6\x77\x67\xba\x57\x0f\xdb\xbb\xfb\x9d\xef\xf6\x55\x23\xa5\xca\xa5\x7c\xb9\x54\x84\x05\x52\xb8\xa2\xca\x31\x84\x41\xcd\x60\x6c\xb4\xbb\xb5\x1b\x4c\x8f\x05\xf3\xcf\x79\xba\xce\x99\x73\x67\xa8\xbb\x33\xef\x9c\x71\xb0\xbd\x8a\x97\x63\x32\x82\xb4\xba\x58\xf2\xf3\x7d\x40\xb7\x99\x8b\xd4\x98\x20\x22\x9e\x69\x10\xc1\x93\x15\xd8\xee\xe1\xd2\x33\x26\x54\x7c\x76\x83\x85\xbLine truncated
|
||||
|
||||
func confLocaleLocale_zhCnIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4579,12 +4579,12 @@ func confLocaleLocale_zhCnIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_zh-CN.ini", size: 43617, mode: os.FileMode(493), modTime: time.Unix(1446027992, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_zh-CN.ini", size: 43500, mode: os.FileMode(493), modTime: time.Unix(1448150150, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _confLocaleLocale_zhHkIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x7d\xfb\x73\x13\x57\xba\xe0\xef\x54\xf1\x3f\xf4\x70\x8b\xca\xbd\x55\xc1\xa9\x24\xfb\xaa\xa9\x28\x77\x13\x92\x99\x64\x37\xc9\xb0\x31\xd9\xa9\xad\x54\x4a\x91\xa5\xb6\xad\x8b\xa4\xd6\xa8\x5b\x38\x9e\x5b\xb7\xca\x86\x18\xcb\x2f\x6c\xc0\x36\x18\x0c\x06\xc2\xc3\x06\x0c\x36\x09\x60\xfc\xe2\x7f\xd9\x51\xb7\xa4\x9f\xf2\x2f\xec\xf7\x3a\xa7\x4f\x3f\x64\xc8\xdc\xdd\xad\x4a\x11\xab\xcf\x77\xde\xdf\xf9\xce\xf7\x3e\xb9\x6a\x35\x5b\xb0\xdd\x7c\xc6\x5f\xd9\x0e\x16\x77\xad\x3f\x3a\x56\xfb\xc1\x8d\xf6\xea\x48\xeb\xea\x8f\xed\xf1\x07\xfe\xe4\x9a\xf5\xc7\xa2\x67\x05\xcb\x33\xfe\xd4\xd2\xe1\x43\x87\x0f\x0d\x3a\x65\x3b\xd3\xb9\xb7\xd8\xb9\x39\x7a\xf8\x50\x21\xe7\x0e\xf6\x39\xb9\x5a\x21\x13\x9c\xbf\xef\x37\x9e\x77\xae\xdf\x6e\x4d\x34\x0e\x1f\xb2\x7f\xa8\x96\x9c\x9a\x0d\x5f\x6f\xb7\x7e\xb9\x0d\x95\xec\x52\x35\xe3\xbf\x7c\x08\xcd\x1d\x3e\xe4\x16\x07\x2a\xd9\x62\x25\xd3\x5a\xda\xe9\x4c\xff\x28\xbf\x9d\xba\x97\xe9\x8c\x8c\xf8\xe3\xdb\xf2\xa1\x5e\xcd\xb4\x57\x77\xfc\x73\x93\x87\x0f\xd5\xec\x81\xa2\xeb\xd9\x35\xfd\x61\xc8\xee\x73\x8b\x9e\x9d\xf1\x1f\x5f\x09\x16\x5f\xb6\x9e\x3f\x6d\x3d\x84\xb1\x9d\xb6\x6b\x6e\xd1\x81\x76\x17\x9e\xfb\x13\x33\x30\x8e\x60\xf9\xd1\xe1\x43\xd5\xdc\x00\x8c\xf7\xe6\x28\x0c\xed\xf0\x21\xcf\x2e\x57\x4b\x39\xa8\x19\xac\xde\xa2\x81\x96\x72\x95\x81\x3a\x42\xf0\xa4\x3b\xa3\x5b\x9d\x9b\x63\x87\x0f\xe5\x6b\x36\x40\x65\x2b\xf6\x50\xe6\x38\xfd\xd9\xd3\xd3\x73\xf8\x50\xdd\xb5\x6b\xd9\x6a\xcd\xe9\x2f\x96\xec\x6c\xae\x52\xc8\x96\x71\x8e\xad\xf9\xd5\xa0\xf1\xbc\xf9\xea\x56\x30\xfa\xc4\x9f\x9d\x0c\xae\xfd\xe2\xdf\xb9\xca\x93\xb0\x0b\x30\xcf\x6c\xce\xcd\xf8\x2f\x9e\xf2\x6c\x19\x18\xd7\x11\x1b\xab\xe4\xca\xaa\xbe\x3f\x37\x03\xcb\x56\xce\x15\x4b\x99\xce\xd9\x67\xad\xc7\x9b\x38\x72\xd7\x1d\x72\x60\x6d\xfd\x27\xe7\x5a\xb7\x77\x71\x1d\xb2\xde\x70\x15\x6a\xdc\xde\x6e\x3f\x98\x51\x5f\xf3\xb9\xaa\x97\x1f\xcc\x65\x3a\x6b\x97\xdb\xeb\x13\xf4\x09\x41\xab\x0e\x2c\x91\x53\x1b\xce\xf8\x23\x13\xfe\xf6\xc3\xc3\x87\x9c\xda\x40\xae\x52\xfc\x6b\xce\xa3\x35\x7a\xf6\x63\xeb\xe5\xfc\xe1\x43\xe5\x62\xad\xe6\xd4\x32\x9d\xd9\x5b\xfe\xd9\xd9\xc3\x87\x60\xc2\x59\xac\x9a\xf1\x27\x9e\xf9\x3b\x80\x10\x1b\x80\x06\xaa\x01\x2c\x2c\x17\x07\x6a\xb8\x7e\x9d\xd1\x17\xad\xfb\x3b\xfe\x9d\xc5\xce\xd9\x55\xb3\xbc\xdf\xa9\x9d\xca\x70\xb5\xe0\x97\xfd\xd6\xfc\x8a\x59\x08\x23\x88\x34\xac\x07\x91\xab\xc0\x16\x50\x71\xeb\xf1\xad\xd6\xdc\xb9\xa0\x71\xc1\x28\xce\x15\xca\xb0\x8a\xd5\x5c\xc5\x2e\x49\xb9\x42\xb3\x5c\x3e\xef\xd4\x2b\x5e\xd6\xb5\x3d\xaf\x58\x19\x80\x75\xde\xfa\x19\xd6\xb2\xbd\xba\xde\xda\x7b\x0c\x5b\x90\xfe\x79\xd8\xa9\xeb\x6d\x84\xd5\x99\x6a\x6e\x6f\xf3\xee\x49\x91\xae\xc6\x3b\xa3\xaa\xd1\x1c\xdc\x6c\xbf\x6d\x03\xb6\x2f\x8f\xc0\x14\x82\x5f\x76\xfc\xa9\x05\xd8\xa8\x7a\xa9\x04\xcb\xf6\x97\xba\xed\x7a\xd0\xd9\x5c\xa3\xb9\xf7\xac\xfd\x70\x2a\xd8\x3c\x73\xf8\x50\xd1\x75\xe1\x73\xc6\x5f\x98\xed\xdc\x9a\xe6\xd1\x63\x53\xf9\x5c\x25\x0f\xd3\xf1\x67\x17\x83\xe7\x0d\xfc\xf0\xad\x6b\xe7\x6a\xf9\xc1\xef\x70\xd4\xf8\x47\x26\x98\x5b\xf6\x37\xa6\x08\xef\x52\x36\x13\xb1\x27\xa3\x90\x89\xfa\x00\x1c\xbb\xeb\x2f\xc2\x38\xf3\x4e\x01\x50\x64\x75\xca\xdf\x9d\x15\x64\xf8\xb6\x58\x71\xbd\x5c\xa9\x04\x8d\xcb\x5f\x70\x72\x26\xda\x3f\x5d\xd7\xc7\xa2\xe8\x95\xe8\x50\x07\x8f\x6e\xf9\x2b\x2f\xda\xb7\xa6\xb9\x1c\x5b\xd9\x06\xb4\x28\x38\xf9\x53\x80\xfa\x78\x8a\xa1\xdb\xcf\xfb\x2d\x58\xa7\xb7\x6a\xb6\x55\xab\x57\x2a\xb0\x52\x40\x34\x06\x5c\x0b\x9a\x2e\x16\x6c\xeb\x13\x82\x7d\xdb\xaa\x96\xec\x9c\x0b\x20\x76\xae\x60\x7d\x90\xb3\xbc\x5c\x6d\xc0\xf6\x32\x47\xb2\x7d\x70\xe4\x4e\x1d\xb1\x06\x6b\x76\x7f\xe6\xc8\x51\xf7\xc8\x87\x7f\xac\x43\xb5\x52\xb1\x62\xbb\x1f\xbc\x93\xfb\xd0\xca\xe7\xa0\x04\x16\x74\xd8\xea\xb3\x01\x9b\x6c\xec\xcb\x02\xfc\xae\x0c\xd8\x56\xae\x32\xec\x0d\x62\x87\xc5\x8a\x05\x7f\xb8\x16\x1e\xee\xdf\xe1\x02\xfd\xa5\x0e\xf4\x20\x5b\xe8\x63\x5a\x46\xe3\xa1\x8f\x35\xdb\xb5\xbe\x1c\xee\xfd\x1f\x5f\xbc\x6d\x9d\x70\x5c\x6f\xa0\x66\xd3\xdf\xf0\x0f\xc0\xbf\x6f\x39\x35\xeb\x64\xf1\x93\x8f\x61\x8d\xa1\x2a\xaf\x42\xb0\xb0\x15\x5c\xba\x0a\x8b\xac\xf6\x1d\x4b\xf0\xd4\xe9\x82\xce\xad\x1b\xfe\x8d\x29\xa4\x84\xae\x17\x7e\x6d\x6e\xed\x04\x6b\x2b\xb2\x33\xfa\xab\xda\x22\x7d\x96\x63\x25\xea\x10\x43\x27\x44\x0c\x74\x31\xd0\x83\xd6\xea\x26\x15\xc8\xb2\xfb\xf7\xce\x04\x37\x96\x83\x33\xab\xcd\xbd\x57\x50\x99\xa7\xf5\xeb\xee\x34\x60\x9a\x7c\xf9\xfc\xab\xaf\xfe\xf4\xc9\xc7\x96\xbf\xbb\x10\x5c\x3a\xdf\xdc\xb9\x0b\xd4\xc8\xaa\x7b\xfd\xff\x25\x3b\x60\x57\xec\x5a\xae\x94\xcd\x17\x2d\x7f\xfd\x72\xeb\xd1\xbd\xce\xb5\x73\x7f\x1b\x01\xec\x74\xdd\x12\x10\x31\xc0\x97\xde\xde\x2f\x2c\xa0\x89\x80\x33\x38\x56\x6f\x30\x1c\x48\xb0\x38\xde\xdc\x79\xde\x7e\xf1\xc4\xdf\xbf\x00\x15\xfe\x52\xc2\x85\x96\x21\x9d\x1c\xb4\x2d\x3c\x45\x16\x56\xb1\x9c\xfe\xf8\xba\x5a\x85\x9c\x97\xeb\x03\x34\x80\x05\xb6\x6b\xb5\x2c\x50\x5f\x6f\x18\x77\x89\xba\xe8\x06\xcc\xad\xc1\x09\xa9\x38\x1e\x20\x81\x45\xb5\xa4\x85\x62\xe5\x74\xae\x54\x2c\xc0\x5e\xa9\x15\x8b\x56\xc5\x4f\x56\xc1\x81\x5d\xc7\xca\x80\xe9\xce\x10\x22\x4f\x2d\x97\x87\xfb\xc3\xb5\x8e\xf4\x1c\x01\x24\x2a\x5Line truncated
|
||||
var _confLocaleLocale_zhHkIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\xbd\x7b\x73\x14\x57\x96\x20\xfe\x7f\x7d\x8a\x6c\x3a\x08\xcf\x44\xd8\x72\xd8\x9e\xdf\x6f\x37\x3a\x5c\x9e\xb5\xa1\xbb\xed\x5d\xdb\xcd\x5a\x78\x3b\x36\x1c\x44\xb9\x54\x95\x92\x72\xa8\xaa\xac\xae\xcc\x42\x96\x27\x26\x42\x02\x0b\x95\x90\x84\x04\x48\x02\x81\xb0\x00\xf3\x10\x0f\x01\xc2\x36\x08\xbd\xf8\x2e\xdb\x95\x59\x55\x7f\xf9\x2b\xec\x79\xdd\x9b\x37\x1f\x12\xf6\xcc\xee\x76\x74\x18\x55\xde\x73\xdf\xe7\x9e\x7b\xde\xb7\x58\xaf\x17\xca\xb6\x57\xca\x07\xab\x5b\xe1\xd2\x8e\xf5\x67\xd7\xea\x3e\xf8\xbe\xbb\x36\xd6\xb9\xfa\x5d\x77\xf2\x41\x70\xee\xbe\xf5\x67\xc7\xb7\xc2\x95\xd9\x60\x7a\x39\x97\x1b\x76\xab\x76\xbe\x77\x77\xa9\x77\x63\x3c\x57\x2e\x7a\xc3\x03\x6e\xb1\x51\xce\x87\xe7\xef\x05\xad\xe7\xbd\xeb\xb7\x3a\x53\xad\x9c\xfd\x4d\xbd\xe2\x36\x6c\xf8\x78\xab\xf3\xd3\xad\xdc\xb0\x5d\xa9\xe7\x83\x97\x0f\xa1\xa5\x9c\xe7\x0c\xd5\x0a\x4e\x2d\xdf\x59\xde\xee\xcd\x7c\xc7\x3f\xdd\xa6\x9f\xef\x8d\x8d\x05\x93\x5b\xfc\xbb\x59\xcf\x77\xd7\xb6\x83\xb3\xe7\x72\x0d\x7b\xc8\xf1\x7c\xbb\xa1\x7e\x8f\xd8\x03\x9e\xe3\xdb\xf9\xe0\xf1\x95\x70\xe9\x65\xe7\xf9\xb3\xce\xc3\xe5\xdc\x29\xbb\xe1\x39\x2e\x34\xb9\xf8\x3c\x98\x9a\x85\xfe\xc3\x95\x47\xb9\x7a\x71\x08\x46\x79\x63\x1c\x46\x94\xf3\xed\x6a\xbd\x52\x84\x6a\xe1\xda\x4d\x1c\x5e\xa5\x58\x1b\x6a\x62\x39\x4f\xb3\x37\xbe\xd9\xbb\x31\x91\x2b\x35\x6c\x80\x29\xd4\xec\x91\x7c\x30\xf5\x73\xb0\xbd\xd5\xd7\xd7\x97\x6b\x7a\x76\xa3\x50\x6f\xb8\x83\x4e\xc5\x2e\x14\x6b\xe5\x42\x15\xe7\xd5\x59\x58\x0b\x5b\xcf\xdb\xaf\x6e\x86\xe3\x4f\x82\xb9\x73\xe1\xb5\x9f\x82\xdb\x57\x69\xec\x76\x19\x26\x57\x28\x7a\xf9\xe0\xc5\x33\x9e\x22\xc3\xe6\xa8\xa5\x5a\xb1\xaa\x2a\x07\xf3\xb3\x39\xbb\x5a\x74\x2a\xf9\xde\x99\x9f\x3b\x8f\x37\x60\xc0\x9e\x37\xe2\xc2\x4a\x06\x4f\xce\x76\x6e\xed\xc0\xd4\x0b\xfe\x68\x1d\xc0\x6f\x6d\x75\x1f\xcc\xca\xc7\x52\xb1\xee\x97\x86\x8b\xf9\xde\xfd\xcb\xdd\xf5\x29\xfc\x02\x70\x75\x17\xd6\xc4\x6d\x8c\xe6\x83\xb1\xa9\x60\xeb\x61\xce\x6d\x0c\x15\x6b\xce\xb7\x45\x9f\x16\xe5\xe7\xef\x3a\x2f\x17\x72\x55\xa7\xd1\x70\x1b\xf9\xde\xdc\xcd\xe0\xcc\x5c\x0e\xe6\x58\xc0\x7a\x32\xd1\x70\xe9\x29\x6c\xb5\xd4\xc6\xb2\xaa\x33\xd4\xc0\xf5\xea\x8d\xbf\xe8\xdc\xdb\x0e\x6e\x2f\xf5\xce\xac\x19\xc5\x83\x6e\xe3\x64\x9e\x2b\x85\x3f\xed\x75\x16\x56\x8d\x32\xe8\x3c\xd6\xaa\xea\xbf\x58\x83\x05\xa7\xd2\xce\xe3\x9b\x9d\xf9\xb3\x61\xeb\x42\x54\x5a\x2c\x57\x61\xd5\xea\xc5\x9a\x5d\x91\x62\x41\xa4\x62\xa9\xe4\x36\x6b\x7e\xc1\xb3\x7d\xdf\xa9\x0d\xc1\xb2\x6e\xfe\x08\x8b\xd7\x5d\x5b\xef\xec\x3e\xce\x65\x7f\x1d\x75\x9b\x7a\xcb\x60\x49\xa6\xdb\x5b\x5b\xbc\x53\x5c\xa2\x2b\xf1\x3e\x48\x25\x1c\xbb\x57\x18\xb4\x6d\xc0\xe4\x95\x31\x18\x7a\xf8\xd3\x76\x30\xbd\x98\xab\x37\x2b\x15\x58\xaa\xbf\x35\x6d\xcf\x87\x7e\xe6\x5b\xed\xdd\x9f\xbb\x0f\xa7\xc3\x8d\xd3\x39\xc7\xf3\xe0\x6b\x3e\x58\x9c\xeb\xdd\x9c\xe1\x51\xe7\x60\x83\x6a\x25\x98\x44\x30\xb7\x14\x3e\x6f\xe5\x72\x5f\x79\x76\xb1\x51\x1a\x3e\x91\xe3\x7f\xf3\xe1\xfc\x4a\xf0\x74\x1a\x11\x2b\xbd\x6d\x88\x20\x32\x28\x6e\x1b\x70\xe8\x4e\xb0\xf4\x38\x57\x72\xcb\x80\x06\x6b\xd3\xc1\xce\x1c\xed\xf8\x57\x4e\xcd\xf3\x8b\x95\xca\x89\x9c\xfc\x01\xc7\x61\xaa\xfb\xc3\x75\x85\xed\x8e\x5f\xa1\x03\x1a\x3e\xba\x19\xac\xbe\xe8\xde\x9c\xe1\x62\x6c\x61\x6b\x2e\x57\x76\x4b\x27\x01\xa7\xf1\x4c\x42\x7f\x9f\x0c\x5a\xb0\x2a\x6f\x34\x6c\xab\xd1\xac\xd5\x60\x5d\xe0\xf4\x0f\x79\x16\x34\xec\x94\x6d\xeb\x28\xc1\xbe\x69\xd5\x2b\x76\xd1\x03\x10\xbb\x58\xb6\xde\x2f\x5a\x7e\xb1\x31\x64\xfb\xf9\x43\x85\x01\x38\x49\x27\x0f\x59\xc3\x0d\x7b\x30\x7f\xe8\xb0\x77\xe8\x83\x3f\x37\xa1\x5a\xc5\xa9\xd9\xde\xfb\x6f\x17\x3f\xb0\x4a\x45\x28\x81\x15\x1c\xb5\x06\x6c\x40\x1a\x1b\xfb\xb2\x00\x7f\x6b\x43\xb6\x55\xac\x8d\xfa\xc3\xd8\xa1\x53\xb3\xe0\x0f\xcf\xc2\x13\xfb\xbb\x1c\xae\x35\x9c\xf0\x42\x79\x80\x69\x12\x0d\x87\x3e\x36\x6c\xcf\xfa\x6c\xb4\xff\xbf\x7f\xfa\xa6\x75\xcc\xf5\xfc\xa1\x86\x4d\x7f\xc3\x7f\x00\xfe\x3d\xcb\x6d\x58\xc7\x9d\xa3\x1f\xf5\xe5\xa0\x26\x2f\x41\xb8\xb8\x19\x5e\xba\x0a\x4b\x2b\x7b\x8c\x05\x78\xa2\xf4\xf7\xde\xcd\xef\x83\xef\xa7\x81\xa0\x79\x7e\xf4\xb1\xbd\xb9\x1d\xde\x5f\xe5\xdd\xd0\x1f\x65\x5b\xf4\x19\x4d\x14\xc8\xe9\x84\x0e\xe8\x84\xeb\x52\x38\xe4\x9d\xb5\x0d\xfc\x2e\xcb\x1d\xdc\x3d\x1d\x7e\xbf\x12\x9e\x5e\x6b\xef\xbe\x82\xaa\x3c\x9f\x5f\x76\x66\x00\xa5\xe4\xcb\x27\x9f\x7f\xfe\x97\xa3\x1f\x59\xc1\xce\x62\x78\xe9\x7c\x7b\xfb\x0e\x90\x17\xab\xe9\x0f\xfe\xe7\xc2\x90\x5d\xb3\x1b\xc5\x4a\xa1\xe4\x58\xc1\xfa\xe5\xce\xa3\xbb\xbd\x6b\x67\xff\x3e\x76\x3a\xe7\x79\x15\x20\x4a\x80\x21\xfd\xfd\x9f\x5a\x40\xe0\x00\x4b\x60\x9c\xfe\x70\x34\x8a\x70\x69\xb2\xbd\xfd\xbc\xfb\xe2\x49\xb0\x77\x21\xe7\xfd\xad\x82\xcb\x2b\xe3\x39\x3e\x6c\x5b\x78\x4e\x2c\xac\x61\xb9\x83\xc9\xd5\xb4\xca\x45\xbf\x38\x00\x7b\xdf\x97\xb3\x1b\x8d\x02\xd0\x51\x7f\x14\xb7\x86\x3a\xd8\x0f\x96\x1b\x83\x93\x50\x73\x7d\xd8\x78\x8b\x6a\x71\x03\x4e\xed\x54\xb1\xe2\x94\x61\x83\xd4\x5a\xc5\x6b\xe2\x27\xab\xec\xc2\x4e\x63\x5d\x40\x6d\x77\x04\xf1\xa5\x51\x2c\xc1\x25\xe0\x59\x87\xfa\x0e\x01\xde\x94\xad\x43\x6f\x1d\xea\xcb\xd5\xdc\x02\xd3\x0d\x24\xcb\x65\xc7\x2b\x0e\x00\x89\xe6\x0b\xa3\xc1\xb4\xef\x7f\x22\xbLine truncated
|
||||
|
||||
func confLocaleLocale_zhHkIniBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@@ -4599,7 +4599,7 @@ func confLocaleLocale_zhHkIni() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "conf/locale/locale_zh-HK.ini", size: 43960, mode: os.FileMode(493), modTime: time.Unix(1446027992, 0)}
|
||||
info := bindataFileInfo{name: "conf/locale/locale_zh-HK.ini", size: 43885, mode: os.FileMode(493), modTime: time.Unix(1448150152, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestSpecSchedule(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, c.expected) {
|
||||
t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
|
||||
t.Errorf("%s => (expected) %v != %v (actual)", c.expr, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,615 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package agent implements a client to an ssh-agent daemon.
|
||||
|
||||
References:
|
||||
[PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
||||
*/
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// Agent represents the capabilities of an ssh-agent.
|
||||
type Agent interface {
|
||||
// List returns the identities known to the agent.
|
||||
List() ([]*Key, error)
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
// in [PROTOCOL.agent] section 2.6.2.
|
||||
Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error)
|
||||
|
||||
// Add adds a private key to the agent.
|
||||
Add(key AddedKey) error
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
Remove(key ssh.PublicKey) error
|
||||
|
||||
// RemoveAll removes all identities.
|
||||
RemoveAll() error
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||
Lock(passphrase []byte) error
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
Unlock(passphrase []byte) error
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
Signers() ([]ssh.Signer, error)
|
||||
}
|
||||
|
||||
// AddedKey describes an SSH key to be added to an Agent.
|
||||
type AddedKey struct {
|
||||
// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
|
||||
// *ecdsa.PrivateKey, which will be inserted into the agent.
|
||||
PrivateKey interface{}
|
||||
// Certificate, if not nil, is communicated to the agent and will be
|
||||
// stored with the key.
|
||||
Certificate *ssh.Certificate
|
||||
// Comment is an optional, free-form string.
|
||||
Comment string
|
||||
// LifetimeSecs, if not zero, is the number of seconds that the
|
||||
// agent will store the key for.
|
||||
LifetimeSecs uint32
|
||||
// ConfirmBeforeUse, if true, requests that the agent confirm with the
|
||||
// user before each use of this key.
|
||||
ConfirmBeforeUse bool
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 3.
|
||||
const (
|
||||
agentRequestV1Identities = 1
|
||||
|
||||
// 3.2 Requests from client to agent for protocol 2 key operations
|
||||
agentAddIdentity = 17
|
||||
agentRemoveIdentity = 18
|
||||
agentRemoveAllIdentities = 19
|
||||
agentAddIdConstrained = 25
|
||||
|
||||
// 3.3 Key-type independent requests from client to agent
|
||||
agentAddSmartcardKey = 20
|
||||
agentRemoveSmartcardKey = 21
|
||||
agentLock = 22
|
||||
agentUnlock = 23
|
||||
agentAddSmartcardKeyConstrained = 26
|
||||
|
||||
// 3.7 Key constraint identifiers
|
||||
agentConstrainLifetime = 1
|
||||
agentConstrainConfirm = 2
|
||||
)
|
||||
|
||||
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
||||
// is a sanity check, not a limit in the spec.
|
||||
const maxAgentResponseBytes = 16 << 20
|
||||
|
||||
// Agent messages:
|
||||
// These structures mirror the wire format of the corresponding ssh agent
|
||||
// messages found in [PROTOCOL.agent].
|
||||
|
||||
// 3.4 Generic replies from agent to client
|
||||
const agentFailure = 5
|
||||
|
||||
type failureAgentMsg struct{}
|
||||
|
||||
const agentSuccess = 6
|
||||
|
||||
type successAgentMsg struct{}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.2.
|
||||
const agentRequestIdentities = 11
|
||||
|
||||
type requestIdentitiesAgentMsg struct{}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.2.
|
||||
const agentIdentitiesAnswer = 12
|
||||
|
||||
type identitiesAnswerAgentMsg struct {
|
||||
NumKeys uint32 `sshtype:"12"`
|
||||
Keys []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.6.2.
|
||||
const agentSignRequest = 13
|
||||
|
||||
type signRequestAgentMsg struct {
|
||||
KeyBlob []byte `sshtype:"13"`
|
||||
Data []byte
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.6.2.
|
||||
|
||||
// 3.6 Replies from agent to client for protocol 2 key operations
|
||||
const agentSignResponse = 14
|
||||
|
||||
type signResponseAgentMsg struct {
|
||||
SigBlob []byte `sshtype:"14"`
|
||||
}
|
||||
|
||||
type publicKey struct {
|
||||
Format string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Key represents a protocol 2 public key as defined in
|
||||
// [PROTOCOL.agent], section 2.5.2.
|
||||
type Key struct {
|
||||
Format string
|
||||
Blob []byte
|
||||
Comment string
|
||||
}
|
||||
|
||||
func clientErr(err error) error {
|
||||
return fmt.Errorf("agent: client error: %v", err)
|
||||
}
|
||||
|
||||
// String returns the storage form of an agent key with the format, base64
|
||||
// encoded serialized key, and the comment if it is not empty.
|
||||
func (k *Key) String() string {
|
||||
s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob)
|
||||
|
||||
if k.Comment != "" {
|
||||
s += " " + k.Comment
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Type returns the public key type.
|
||||
func (k *Key) Type() string {
|
||||
return k.Format
|
||||
}
|
||||
|
||||
// Marshal returns key blob to satisfy the ssh.PublicKey interface.
|
||||
func (k *Key) Marshal() []byte {
|
||||
return k.Blob
|
||||
}
|
||||
|
||||
// Verify satisfies the ssh.PublicKey interface, but is not
|
||||
// implemented for agent keys.
|
||||
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
||||
return errors.New("agent: agent key does not know how to verify")
|
||||
}
|
||||
|
||||
type wireKey struct {
|
||||
Format string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
||||
var record struct {
|
||||
Blob []byte
|
||||
Comment string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal(in, &record); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(record.Blob, &wk); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Key{
|
||||
Format: wk.Format,
|
||||
Blob: record.Blob,
|
||||
Comment: record.Comment,
|
||||
}, record.Rest, nil
|
||||
}
|
||||
|
||||
// client is a client for an ssh-agent process.
|
||||
type client struct {
|
||||
// conn is typically a *net.UnixConn
|
||||
conn io.ReadWriter
|
||||
// mu is used to prevent concurrent access to the agent
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewClient returns an Agent that talks to an ssh-agent process over
|
||||
// the given connection.
|
||||
func NewClient(rw io.ReadWriter) Agent {
|
||||
return &client{conn: rw}
|
||||
}
|
||||
|
||||
// call sends an RPC to the agent. On success, the reply is
|
||||
// unmarshaled into reply and replyType is set to the first byte of
|
||||
// the reply, which contains the type of the message.
|
||||
func (c *client) call(req []byte) (reply interface{}, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
msg := make([]byte, 4+len(req))
|
||||
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
||||
copy(msg[4:], req)
|
||||
if _, err = c.conn.Write(msg); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
|
||||
var respSizeBuf [4]byte
|
||||
if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
respSize := binary.BigEndian.Uint32(respSizeBuf[:])
|
||||
if respSize > maxAgentResponseBytes {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, respSize)
|
||||
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
reply, err = unmarshal(buf)
|
||||
if err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *client) simpleCall(req []byte) error {
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
func (c *client) RemoveAll() error {
|
||||
return c.simpleCall([]byte{agentRemoveAllIdentities})
|
||||
}
|
||||
|
||||
func (c *client) Remove(key ssh.PublicKey) error {
|
||||
req := ssh.Marshal(&agentRemoveIdentityMsg{
|
||||
KeyBlob: key.Marshal(),
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
func (c *client) Lock(passphrase []byte) error {
|
||||
req := ssh.Marshal(&agentLockMsg{
|
||||
Passphrase: passphrase,
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
func (c *client) Unlock(passphrase []byte) error {
|
||||
req := ssh.Marshal(&agentUnlockMsg{
|
||||
Passphrase: passphrase,
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (c *client) List() ([]*Key, error) {
|
||||
// see [PROTOCOL.agent] section 2.5.2.
|
||||
req := []byte{agentRequestIdentities}
|
||||
|
||||
msg, err := c.call(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *identitiesAnswerAgentMsg:
|
||||
if msg.NumKeys > maxAgentResponseBytes/8 {
|
||||
return nil, errors.New("agent: too many keys in agent reply")
|
||||
}
|
||||
keys := make([]*Key, msg.NumKeys)
|
||||
data := msg.Keys
|
||||
for i := uint32(0); i < msg.NumKeys; i++ {
|
||||
var key *Key
|
||||
var err error
|
||||
if key, data, err = parseKey(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[i] = key
|
||||
}
|
||||
return keys, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to list keys")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
// in [PROTOCOL.agent] section 2.6.2.
|
||||
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
req := ssh.Marshal(signRequestAgentMsg{
|
||||
KeyBlob: key.Marshal(),
|
||||
Data: data,
|
||||
})
|
||||
|
||||
msg, err := c.call(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *signResponseAgentMsg:
|
||||
var sig ssh.Signature
|
||||
if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sig, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to sign challenge")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// unmarshal parses an agent message in packet, returning the parsed
|
||||
// form and the message type of packet.
|
||||
func unmarshal(packet []byte) (interface{}, error) {
|
||||
if len(packet) < 1 {
|
||||
return nil, errors.New("agent: empty packet")
|
||||
}
|
||||
var msg interface{}
|
||||
switch packet[0] {
|
||||
case agentFailure:
|
||||
return new(failureAgentMsg), nil
|
||||
case agentSuccess:
|
||||
return new(successAgentMsg), nil
|
||||
case agentIdentitiesAnswer:
|
||||
msg = new(identitiesAnswerAgentMsg)
|
||||
case agentSignResponse:
|
||||
msg = new(signResponseAgentMsg)
|
||||
default:
|
||||
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
||||
}
|
||||
if err := ssh.Unmarshal(packet, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
type rsaKeyMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type dsaKeyMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
G *big.Int
|
||||
Y *big.Int
|
||||
X *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ecdsaKeyMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
Curve string
|
||||
KeyBytes []byte
|
||||
D *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Insert adds a private key to the agent.
|
||||
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
||||
var req []byte
|
||||
switch k := s.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if len(k.Primes) != 2 {
|
||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||
}
|
||||
k.Precompute()
|
||||
req = ssh.Marshal(rsaKeyMsg{
|
||||
Type: ssh.KeyAlgoRSA,
|
||||
N: k.N,
|
||||
E: big.NewInt(int64(k.E)),
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaKeyMsg{
|
||||
Type: ssh.KeyAlgoDSA,
|
||||
P: k.P,
|
||||
Q: k.Q,
|
||||
G: k.G,
|
||||
Y: k.Y,
|
||||
X: k.X,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *ecdsa.PrivateKey:
|
||||
nistID := fmt.Sprintf("nistp%d", k.Params().BitSize)
|
||||
req = ssh.Marshal(ecdsaKeyMsg{
|
||||
Type: "ecdsa-sha2-" + nistID,
|
||||
Curve: nistID,
|
||||
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
|
||||
D: k.D,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||
}
|
||||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
type rsaCertMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
CertBytes []byte
|
||||
D *big.Int
|
||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type dsaCertMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
CertBytes []byte
|
||||
X *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ecdsaCertMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
CertBytes []byte
|
||||
D *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Insert adds a private key to the agent. If a certificate is given,
|
||||
// that certificate is added instead as public key.
|
||||
func (c *client) Add(key AddedKey) error {
|
||||
var constraints []byte
|
||||
|
||||
if secs := key.LifetimeSecs; secs != 0 {
|
||||
constraints = append(constraints, agentConstrainLifetime)
|
||||
|
||||
var secsBytes [4]byte
|
||||
binary.BigEndian.PutUint32(secsBytes[:], secs)
|
||||
constraints = append(constraints, secsBytes[:]...)
|
||||
}
|
||||
|
||||
if key.ConfirmBeforeUse {
|
||||
constraints = append(constraints, agentConstrainConfirm)
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert == nil {
|
||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||
} else {
|
||||
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
|
||||
var req []byte
|
||||
switch k := s.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if len(k.Primes) != 2 {
|
||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||
}
|
||||
k.Precompute()
|
||||
req = ssh.Marshal(rsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
X: k.X,
|
||||
Comments: comment,
|
||||
})
|
||||
case *ecdsa.PrivateKey:
|
||||
req = ssh.Marshal(ecdsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
D: k.D,
|
||||
Comments: comment,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||
}
|
||||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||
return errors.New("agent: signer and cert have different public key")
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
// Signers provides a callback for client authentication.
|
||||
func (c *client) Signers() ([]ssh.Signer, error) {
|
||||
keys, err := c.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []ssh.Signer
|
||||
for _, k := range keys {
|
||||
result = append(result, &agentKeyringSigner{c, k})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type agentKeyringSigner struct {
|
||||
agent *client
|
||||
pub ssh.PublicKey
|
||||
}
|
||||
|
||||
func (s *agentKeyringSigner) PublicKey() ssh.PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
||||
// The agent has its own entropy source, so the rand argument is ignored.
|
||||
return s.agent.Sign(s.pub, data)
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// startAgent executes ssh-agent, and returns a Agent interface to it.
|
||||
func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
||||
if testing.Short() {
|
||||
// ssh-agent is not always available, and the key
|
||||
// types supported vary by platform.
|
||||
t.Skip("skipping test due to -short")
|
||||
}
|
||||
|
||||
bin, err := exec.LookPath("ssh-agent")
|
||||
if err != nil {
|
||||
t.Skip("could not find ssh-agent")
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, "-s")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cmd.Output: %v", err)
|
||||
}
|
||||
|
||||
/* Output looks like:
|
||||
|
||||
SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
|
||||
SSH_AGENT_PID=15542; export SSH_AGENT_PID;
|
||||
echo Agent pid 15542;
|
||||
*/
|
||||
fields := bytes.Split(out, []byte(";"))
|
||||
line := bytes.SplitN(fields[0], []byte("="), 2)
|
||||
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||
if string(line[0]) != "SSH_AUTH_SOCK" {
|
||||
t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
|
||||
}
|
||||
socket = string(line[1])
|
||||
|
||||
line = bytes.SplitN(fields[2], []byte("="), 2)
|
||||
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||
if string(line[0]) != "SSH_AGENT_PID" {
|
||||
t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
|
||||
}
|
||||
pidStr := line[1]
|
||||
pid, err := strconv.Atoi(string(pidStr))
|
||||
if err != nil {
|
||||
t.Fatalf("Atoi(%q): %v", pidStr, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", string(socket))
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial: %v", err)
|
||||
}
|
||||
|
||||
ac := NewClient(conn)
|
||||
return ac, socket, func() {
|
||||
proc, _ := os.FindProcess(pid)
|
||||
if proc != nil {
|
||||
proc.Kill()
|
||||
}
|
||||
conn.Close()
|
||||
os.RemoveAll(filepath.Dir(socket))
|
||||
}
|
||||
}
|
||||
|
||||
func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||
}
|
||||
|
||||
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSignerFromKey(%T): %v", key, err)
|
||||
}
|
||||
// The agent should start up empty.
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Fatalf("RequestIdentities: %v", err)
|
||||
} else if len(keys) > 0 {
|
||||
t.Fatalf("got %d keys, want 0: %v", len(keys), keys)
|
||||
}
|
||||
|
||||
// Attempt to insert the key, with certificate if specified.
|
||||
var pubKey ssh.PublicKey
|
||||
if cert != nil {
|
||||
err = agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
Certificate: cert,
|
||||
Comment: "comment",
|
||||
LifetimeSecs: lifetimeSecs,
|
||||
})
|
||||
pubKey = cert
|
||||
} else {
|
||||
err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
|
||||
pubKey = signer.PublicKey()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("insert(%T): %v", key, err)
|
||||
}
|
||||
|
||||
// Did the key get inserted successfully?
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
} else if len(keys) != 1 {
|
||||
t.Fatalf("got %v, want 1 key", keys)
|
||||
} else if keys[0].Comment != "comment" {
|
||||
t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment")
|
||||
} else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) {
|
||||
t.Fatalf("key mismatch")
|
||||
}
|
||||
|
||||
// Can the agent make a valid signature?
|
||||
data := []byte("hello")
|
||||
sig, err := agent.Sign(pubKey, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(%s): %v", pubKey.Type(), err)
|
||||
}
|
||||
|
||||
if err := pubKey.Verify(data, sig); err != nil {
|
||||
t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent(t *testing.T) {
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
|
||||
testAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
cert := &ssh.Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
CertType: ssh.UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
testAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||
}
|
||||
|
||||
func TestConstraints(t *testing.T) {
|
||||
testAgent(t, testPrivateKeys["rsa"], nil, 3600 /* lifetime in seconds */)
|
||||
}
|
||||
|
||||
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||
// a write.)
|
||||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c2, err := listener.Accept()
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c1, c2, nil
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
|
||||
serverConf := ssh.ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("pubkey rejected")
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{}
|
||||
conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
|
||||
conn, _, _, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestLockClient(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
testLockAgent(agent, t)
|
||||
}
|
||||
|
||||
func testLockAgent(agent Agent, t *testing.T) {
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 2 {
|
||||
t.Errorf("Want 2 keys, got %v", keys)
|
||||
}
|
||||
|
||||
passphrase := []byte("secret")
|
||||
if err := agent.Lock(passphrase); err != nil {
|
||||
t.Errorf("Lock: %v", err)
|
||||
}
|
||||
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 0 {
|
||||
t.Errorf("Want 0 keys, got %v", keys)
|
||||
}
|
||||
|
||||
signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"])
|
||||
if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil {
|
||||
t.Fatalf("Sign did not fail")
|
||||
}
|
||||
|
||||
if err := agent.Remove(signer.PublicKey()); err == nil {
|
||||
t.Fatalf("Remove did not fail")
|
||||
}
|
||||
|
||||
if err := agent.RemoveAll(); err == nil {
|
||||
t.Fatalf("RemoveAll did not fail")
|
||||
}
|
||||
|
||||
if err := agent.Unlock(nil); err == nil {
|
||||
t.Errorf("Unlock with wrong passphrase succeeded")
|
||||
}
|
||||
if err := agent.Unlock(passphrase); err != nil {
|
||||
t.Errorf("Unlock: %v", err)
|
||||
}
|
||||
|
||||
if err := agent.Remove(signer.PublicKey()); err != nil {
|
||||
t.Fatalf("Remove: %v", err)
|
||||
}
|
||||
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 1 {
|
||||
t.Errorf("Want 1 keys, got %v", keys)
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// RequestAgentForwarding sets up agent forwarding for the session.
|
||||
// ForwardToAgent or ForwardToRemote should be called to route
|
||||
// the authentication requests.
|
||||
func RequestAgentForwarding(session *ssh.Session) error {
|
||||
ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("forwarding request denied")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForwardToAgent routes authentication requests to the given keyring.
|
||||
func ForwardToAgent(client *ssh.Client, keyring Agent) error {
|
||||
channels := client.HandleChannelOpen(channelType)
|
||||
if channels == nil {
|
||||
return errors.New("agent: already have handler for " + channelType)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for ch := range channels {
|
||||
channel, reqs, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go func() {
|
||||
ServeAgent(keyring, channel)
|
||||
channel.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
const channelType = "auth-agent@openssh.com"
|
||||
|
||||
// ForwardToRemote routes authentication requests to the ssh-agent
|
||||
// process serving on the given unix socket.
|
||||
func ForwardToRemote(client *ssh.Client, addr string) error {
|
||||
channels := client.HandleChannelOpen(channelType)
|
||||
if channels == nil {
|
||||
return errors.New("agent: already have handler for " + channelType)
|
||||
}
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
go func() {
|
||||
for ch := range channels {
|
||||
channel, reqs, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go forwardUnixSocket(channel, addr)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardUnixSocket(channel ssh.Channel, addr string) {
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
io.Copy(conn, channel)
|
||||
conn.(*net.UnixConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(channel, conn)
|
||||
channel.CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
conn.Close()
|
||||
channel.Close()
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
type privKey struct {
|
||||
signer ssh.Signer
|
||||
comment string
|
||||
}
|
||||
|
||||
type keyring struct {
|
||||
mu sync.Mutex
|
||||
keys []privKey
|
||||
|
||||
locked bool
|
||||
passphrase []byte
|
||||
}
|
||||
|
||||
var errLocked = errors.New("agent: locked")
|
||||
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
func NewKeyring() Agent {
|
||||
return &keyring{}
|
||||
}
|
||||
|
||||
// RemoveAll removes all identities.
|
||||
func (r *keyring) RemoveAll() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
r.keys = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
func (r *keyring) Remove(key ssh.PublicKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
want := key.Marshal()
|
||||
found := false
|
||||
for i := 0; i < len(r.keys); {
|
||||
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
||||
found = true
|
||||
r.keys[i] = r.keys[len(r.keys)-1]
|
||||
r.keys = r.keys[len(r.keys)-1:]
|
||||
continue
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("agent: key not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||
func (r *keyring) Lock(passphrase []byte) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
r.locked = true
|
||||
r.passphrase = passphrase
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
func (r *keyring) Unlock(passphrase []byte) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.locked {
|
||||
return errors.New("agent: not locked")
|
||||
}
|
||||
if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
||||
return fmt.Errorf("agent: incorrect passphrase")
|
||||
}
|
||||
|
||||
r.locked = false
|
||||
r.passphrase = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (r *keyring) List() ([]*Key, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
// section 2.7: locked agents return empty.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ids []*Key
|
||||
for _, k := range r.keys {
|
||||
pub := k.signer.PublicKey()
|
||||
ids = append(ids, &Key{
|
||||
Format: pub.Type(),
|
||||
Blob: pub.Marshal(),
|
||||
Comment: k.comment})
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Insert adds a private key to the keyring. If a certificate
|
||||
// is given, that certificate is added as public key. Note that
|
||||
// any constraints given are ignored.
|
||||
func (r *keyring) Add(key AddedKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert != nil {
|
||||
signer, err = ssh.NewCertSigner(cert, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.keys = append(r.keys, privKey{signer, key.Comment})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign returns a signature for the data.
|
||||
func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return nil, errLocked
|
||||
}
|
||||
|
||||
wanted := key.Marshal()
|
||||
for _, k := range r.keys {
|
||||
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
||||
return k.signer.Sign(rand.Reader, data)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
func (r *keyring) Signers() ([]ssh.Signer, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return nil, errLocked
|
||||
}
|
||||
|
||||
s := make([]ssh.Signer, 0, len(r.keys))
|
||||
for _, k := range r.keys {
|
||||
s = append(s, k.signer)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// Server wraps an Agent and uses it to implement the agent side of
|
||||
// the SSH-agent, wire protocol.
|
||||
type server struct {
|
||||
agent Agent
|
||||
}
|
||||
|
||||
func (s *server) processRequestBytes(reqData []byte) []byte {
|
||||
rep, err := s.processRequest(reqData)
|
||||
if err != nil {
|
||||
if err != errLocked {
|
||||
// TODO(hanwen): provide better logging interface?
|
||||
log.Printf("agent %d: %v", reqData[0], err)
|
||||
}
|
||||
return []byte{agentFailure}
|
||||
}
|
||||
|
||||
if err == nil && rep == nil {
|
||||
return []byte{agentSuccess}
|
||||
}
|
||||
|
||||
return ssh.Marshal(rep)
|
||||
}
|
||||
|
||||
func marshalKey(k *Key) []byte {
|
||||
var record struct {
|
||||
Blob []byte
|
||||
Comment string
|
||||
}
|
||||
record.Blob = k.Marshal()
|
||||
record.Comment = k.Comment
|
||||
|
||||
return ssh.Marshal(&record)
|
||||
}
|
||||
|
||||
type agentV1IdentityMsg struct {
|
||||
Numkeys uint32 `sshtype:"2"`
|
||||
}
|
||||
|
||||
type agentRemoveIdentityMsg struct {
|
||||
KeyBlob []byte `sshtype:"18"`
|
||||
}
|
||||
|
||||
type agentLockMsg struct {
|
||||
Passphrase []byte `sshtype:"22"`
|
||||
}
|
||||
|
||||
type agentUnlockMsg struct {
|
||||
Passphrase []byte `sshtype:"23"`
|
||||
}
|
||||
|
||||
func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||
switch data[0] {
|
||||
case agentRequestV1Identities:
|
||||
return &agentV1IdentityMsg{0}, nil
|
||||
case agentRemoveIdentity:
|
||||
var req agentRemoveIdentityMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
||||
|
||||
case agentRemoveAllIdentities:
|
||||
return nil, s.agent.RemoveAll()
|
||||
|
||||
case agentLock:
|
||||
var req agentLockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.agent.Lock(req.Passphrase)
|
||||
|
||||
case agentUnlock:
|
||||
var req agentLockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.agent.Unlock(req.Passphrase)
|
||||
|
||||
case agentSignRequest:
|
||||
var req signRequestAgentMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := &Key{
|
||||
Format: wk.Format,
|
||||
Blob: req.KeyBlob,
|
||||
}
|
||||
|
||||
sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
||||
case agentRequestIdentities:
|
||||
keys, err := s.agent.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rep := identitiesAnswerAgentMsg{
|
||||
NumKeys: uint32(len(keys)),
|
||||
}
|
||||
for _, k := range keys {
|
||||
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
||||
}
|
||||
return rep, nil
|
||||
case agentAddIdentity:
|
||||
return nil, s.insertIdentity(data)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
||||
}
|
||||
|
||||
func (s *server) insertIdentity(req []byte) error {
|
||||
var record struct {
|
||||
Type string `sshtype:"17"`
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
if err := ssh.Unmarshal(req, &record); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch record.Type {
|
||||
case ssh.KeyAlgoRSA:
|
||||
var k rsaKeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
priv := rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
E: int(k.E.Int64()),
|
||||
N: k.N,
|
||||
},
|
||||
D: k.D,
|
||||
Primes: []*big.Int{k.P, k.Q},
|
||||
}
|
||||
priv.Precompute()
|
||||
|
||||
return s.agent.Add(AddedKey{PrivateKey: &priv, Comment: k.Comments})
|
||||
}
|
||||
return fmt.Errorf("not implemented: %s", record.Type)
|
||||
}
|
||||
|
||||
// ServeAgent serves the agent protocol on the given connection. It
|
||||
// returns when an I/O error occurs.
|
||||
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
||||
s := &server{agent}
|
||||
|
||||
var length [4]byte
|
||||
for {
|
||||
if _, err := io.ReadFull(c, length[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
l := binary.BigEndian.Uint32(length[:])
|
||||
if l > maxAgentResponseBytes {
|
||||
// We also cap requests.
|
||||
return fmt.Errorf("agent: request too large: %d", l)
|
||||
}
|
||||
|
||||
req := make([]byte, l)
|
||||
if _, err := io.ReadFull(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repData := s.processRequestBytes(req)
|
||||
if len(repData) > maxAgentResponseBytes {
|
||||
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
||||
if _, err := c.Write(length[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.Write(repData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
client := NewClient(c1)
|
||||
|
||||
go ServeAgent(NewKeyring(), c2)
|
||||
|
||||
testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
|
||||
}
|
||||
|
||||
func TestLockServer(t *testing.T) {
|
||||
testLockAgent(NewKeyring(), t)
|
||||
}
|
||||
|
||||
func TestSetupForwardAgent(t *testing.T) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
_, socket, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
serverConf := ssh.ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
incoming := make(chan *ssh.ServerConn, 1)
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
incoming <- conn
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{}
|
||||
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
client := ssh.NewClient(conn, chans, reqs)
|
||||
|
||||
if err := ForwardToRemote(client, socket); err != nil {
|
||||
t.Fatalf("SetupForwardAgent: %v", err)
|
||||
}
|
||||
|
||||
server := <-incoming
|
||||
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
agentClient := NewClient(ch)
|
||||
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
|
||||
conn.Close()
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places:
|
||||
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
|
||||
// instances.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
"github.com/gogits/gogs/modules/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
var (
|
||||
testPrivateKeys map[string]interface{}
|
||||
testSigners map[string]ssh.Signer
|
||||
testPublicKeys map[string]ssh.PublicKey
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
n := len(testdata.PEMBytes)
|
||||
testPrivateKeys = make(map[string]interface{}, n)
|
||||
testSigners = make(map[string]ssh.Signer, n)
|
||||
testPublicKeys = make(map[string]ssh.PublicKey, n)
|
||||
for t, k := range testdata.PEMBytes {
|
||||
testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
|
||||
}
|
||||
testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
|
||||
}
|
||||
testPublicKeys[t] = testSigners[t].PublicKey()
|
||||
}
|
||||
|
||||
// Create a cert and sign it for use in tests.
|
||||
testCert := &ssh.Certificate{
|
||||
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
|
||||
ValidAfter: 0, // unix epoch
|
||||
ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
|
||||
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
Key: testPublicKeys["ecdsa"],
|
||||
SignatureKey: testPublicKeys["rsa"],
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
},
|
||||
}
|
||||
testCert.SignCert(rand.Reader, testSigners["rsa"])
|
||||
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
|
||||
testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
*ServerConn
|
||||
chans <-chan NewChannel
|
||||
}
|
||||
|
||||
func newServer(c net.Conn, conf *ServerConfig) (*server, error) {
|
||||
sconn, chans, reqs, err := NewServerConn(c, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
return &server{sconn, chans}, nil
|
||||
}
|
||||
|
||||
func (s *server) Accept() (NewChannel, error) {
|
||||
n, ok := <-s.chans
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func sshPipe() (Conn, *server, error) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
serverConf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
done := make(chan *server, 1)
|
||||
go func() {
|
||||
server, err := newServer(c2, &serverConf)
|
||||
if err != nil {
|
||||
done <- nil
|
||||
}
|
||||
done <- server
|
||||
}()
|
||||
|
||||
client, _, reqs, err := NewClientConn(c1, "", &clientConf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
server := <-done
|
||||
if server == nil {
|
||||
return nil, nil, errors.New("server handshake failed.")
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
|
||||
return client, server, nil
|
||||
}
|
||||
|
||||
func BenchmarkEndToEnd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
client, server, err := sshPipe()
|
||||
if err != nil {
|
||||
b.Fatalf("sshPipe: %v", err)
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
size := (1 << 20)
|
||||
input := make([]byte, size)
|
||||
output := make([]byte, size)
|
||||
b.SetBytes(int64(size))
|
||||
done := make(chan int, 1)
|
||||
|
||||
go func() {
|
||||
newCh, err := server.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Client: %v", err)
|
||||
}
|
||||
ch, incoming, err := newCh.Accept()
|
||||
go DiscardRequests(incoming)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := io.ReadFull(ch, output); err != nil {
|
||||
b.Fatalf("ReadFull: %v", err)
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
done <- 1
|
||||
}()
|
||||
|
||||
ch, in, err := client.OpenChannel("speed", nil)
|
||||
if err != nil {
|
||||
b.Fatalf("OpenChannel: %v", err)
|
||||
}
|
||||
go DiscardRequests(in)
|
||||
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := ch.Write(input); err != nil {
|
||||
b.Fatalf("WriteFull: %v", err)
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
b.StopTimer()
|
||||
|
||||
<-done
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// buffer provides a linked list buffer for data exchange
|
||||
// between producer and consumer. Theoretically the buffer is
|
||||
// of unlimited capacity as it does no allocation of its own.
|
||||
type buffer struct {
|
||||
// protects concurrent access to head, tail and closed
|
||||
*sync.Cond
|
||||
|
||||
head *element // the buffer that will be read first
|
||||
tail *element // the buffer that will be read last
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
// An element represents a single link in a linked list.
|
||||
type element struct {
|
||||
buf []byte
|
||||
next *element
|
||||
}
|
||||
|
||||
// newBuffer returns an empty buffer that is not closed.
|
||||
func newBuffer() *buffer {
|
||||
e := new(element)
|
||||
b := &buffer{
|
||||
Cond: newCond(),
|
||||
head: e,
|
||||
tail: e,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// write makes buf available for Read to receive.
|
||||
// buf must not be modified after the call to write.
|
||||
func (b *buffer) write(buf []byte) {
|
||||
b.Cond.L.Lock()
|
||||
e := &element{buf: buf}
|
||||
b.tail.next = e
|
||||
b.tail = e
|
||||
b.Cond.Signal()
|
||||
b.Cond.L.Unlock()
|
||||
}
|
||||
|
||||
// eof closes the buffer. Reads from the buffer once all
|
||||
// the data has been consumed will receive os.EOF.
|
||||
func (b *buffer) eof() error {
|
||||
b.Cond.L.Lock()
|
||||
b.closed = true
|
||||
b.Cond.Signal()
|
||||
b.Cond.L.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads data from the internal buffer in buf. Reads will block
|
||||
// if no data is available, or until the buffer is closed.
|
||||
func (b *buffer) Read(buf []byte) (n int, err error) {
|
||||
b.Cond.L.Lock()
|
||||
defer b.Cond.L.Unlock()
|
||||
|
||||
for len(buf) > 0 {
|
||||
// if there is data in b.head, copy it
|
||||
if len(b.head.buf) > 0 {
|
||||
r := copy(buf, b.head.buf)
|
||||
buf, b.head.buf = buf[r:], b.head.buf[r:]
|
||||
n += r
|
||||
continue
|
||||
}
|
||||
// if there is a next buffer, make it the head
|
||||
if len(b.head.buf) == 0 && b.head != b.tail {
|
||||
b.head = b.head.next
|
||||
continue
|
||||
}
|
||||
|
||||
// if at least one byte has been copied, return
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// if nothing was read, and there is nothing outstanding
|
||||
// check to see if the buffer is closed.
|
||||
if b.closed {
|
||||
err = io.EOF
|
||||
break
|
||||
}
|
||||
// out of buffers, wait for producer
|
||||
b.Cond.Wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var alphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
func TestBufferReadwrite(t *testing.T) {
|
||||
b := newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
r, _ := b.Read(make([]byte, 10))
|
||||
if r != 10 {
|
||||
t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:5])
|
||||
r, _ = b.Read(make([]byte, 10))
|
||||
if r != 5 {
|
||||
t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
r, _ = b.Read(make([]byte, 5))
|
||||
if r != 5 {
|
||||
t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:5])
|
||||
b.write(alphabet[5:15])
|
||||
r, _ = b.Read(make([]byte, 10))
|
||||
r2, _ := b.Read(make([]byte, 10))
|
||||
if r != 10 || r2 != 5 || 15 != r+r2 {
|
||||
t.Fatal("Expected written == read == 15")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferClose(t *testing.T) {
|
||||
b := newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
_, err := b.Read(make([]byte, 5))
|
||||
if err != nil {
|
||||
t.Fatal("expected read of 5 to not return EOF")
|
||||
}
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
r, err := b.Read(make([]byte, 5))
|
||||
r2, err2 := b.Read(make([]byte, 10))
|
||||
if r != 5 || r2 != 5 || err != nil || err2 != nil {
|
||||
t.Fatal("expected reads of 5 and 5")
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
r, err = b.Read(make([]byte, 5))
|
||||
r2, err2 = b.Read(make([]byte, 10))
|
||||
r3, err3 := b.Read(make([]byte, 10))
|
||||
if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
|
||||
t.Fatal("expected reads of 5 and 5 and 0, with EOF")
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(make([]byte, 5))
|
||||
b.write(make([]byte, 10))
|
||||
b.eof()
|
||||
r, err = b.Read(make([]byte, 9))
|
||||
r2, err2 = b.Read(make([]byte, 3))
|
||||
r3, err3 = b.Read(make([]byte, 3))
|
||||
r4, err4 := b.Read(make([]byte, 10))
|
||||
if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
|
||||
t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
|
||||
}
|
||||
if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
|
||||
t.Fatal("Expected written == read == 15", r, r2, r3, r4)
|
||||
}
|
||||
}
|
||||
@@ -1,501 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
||||
// for certificate types supported by this package.
|
||||
const (
|
||||
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
||||
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
||||
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
||||
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
||||
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
||||
)
|
||||
|
||||
// Certificate types distinguish between host and user
|
||||
// certificates. The values can be set in the CertType field of
|
||||
// Certificate.
|
||||
const (
|
||||
UserCert = 1
|
||||
HostCert = 2
|
||||
)
|
||||
|
||||
// Signature represents a cryptographic signature.
|
||||
type Signature struct {
|
||||
Format string
|
||||
Blob []byte
|
||||
}
|
||||
|
||||
// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that
|
||||
// a certificate does not expire.
|
||||
const CertTimeInfinity = 1<<64 - 1
|
||||
|
||||
// An Certificate represents an OpenSSH certificate as defined in
|
||||
// [PROTOCOL.certkeys]?rev=1.8.
|
||||
type Certificate struct {
|
||||
Nonce []byte
|
||||
Key PublicKey
|
||||
Serial uint64
|
||||
CertType uint32
|
||||
KeyId string
|
||||
ValidPrincipals []string
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
Permissions
|
||||
Reserved []byte
|
||||
SignatureKey PublicKey
|
||||
Signature *Signature
|
||||
}
|
||||
|
||||
// genericCertData holds the key-independent part of the certificate data.
|
||||
// Overall, certificates contain an nonce, public key fields and
|
||||
// key-independent fields.
|
||||
type genericCertData struct {
|
||||
Serial uint64
|
||||
CertType uint32
|
||||
KeyId string
|
||||
ValidPrincipals []byte
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
CriticalOptions []byte
|
||||
Extensions []byte
|
||||
Reserved []byte
|
||||
SignatureKey []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
func marshalStringList(namelist []string) []byte {
|
||||
var to []byte
|
||||
for _, name := range namelist {
|
||||
s := struct{ N string }{name}
|
||||
to = append(to, Marshal(&s)...)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
type optionsTuple struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type optionsTupleValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// serialize a map of critical options or extensions
|
||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||
// we need two length prefixes for a non-empty string value
|
||||
func marshalTuples(tups map[string]string) []byte {
|
||||
keys := make([]string, 0, len(tups))
|
||||
for key := range tups {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var ret []byte
|
||||
for _, key := range keys {
|
||||
s := optionsTuple{Key: key}
|
||||
if value := tups[key]; len(value) > 0 {
|
||||
s.Value = Marshal(&optionsTupleValue{value})
|
||||
}
|
||||
ret = append(ret, Marshal(&s)...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||
// we need two length prefixes for a non-empty option value
|
||||
func parseTuples(in []byte) (map[string]string, error) {
|
||||
tups := map[string]string{}
|
||||
var lastKey string
|
||||
var haveLastKey bool
|
||||
|
||||
for len(in) > 0 {
|
||||
var key, val, extra []byte
|
||||
var ok bool
|
||||
|
||||
if key, in, ok = parseString(in); !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
keyStr := string(key)
|
||||
// according to [PROTOCOL.certkeys], the names must be in
|
||||
// lexical order.
|
||||
if haveLastKey && keyStr <= lastKey {
|
||||
return nil, fmt.Errorf("ssh: certificate options are not in lexical order")
|
||||
}
|
||||
lastKey, haveLastKey = keyStr, true
|
||||
// the next field is a data field, which if non-empty has a string embedded
|
||||
if val, in, ok = parseString(in); !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
if len(val) > 0 {
|
||||
val, extra, ok = parseString(val)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
if len(extra) > 0 {
|
||||
return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value")
|
||||
}
|
||||
tups[keyStr] = string(val)
|
||||
} else {
|
||||
tups[keyStr] = ""
|
||||
}
|
||||
}
|
||||
return tups, nil
|
||||
}
|
||||
|
||||
func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
||||
nonce, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
|
||||
key, rest, err := parsePubKey(rest, privAlgo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var g genericCertData
|
||||
if err := Unmarshal(rest, &g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Certificate{
|
||||
Nonce: nonce,
|
||||
Key: key,
|
||||
Serial: g.Serial,
|
||||
CertType: g.CertType,
|
||||
KeyId: g.KeyId,
|
||||
ValidAfter: g.ValidAfter,
|
||||
ValidBefore: g.ValidBefore,
|
||||
}
|
||||
|
||||
for principals := g.ValidPrincipals; len(principals) > 0; {
|
||||
principal, rest, ok := parseString(principals)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
c.ValidPrincipals = append(c.ValidPrincipals, string(principal))
|
||||
principals = rest
|
||||
}
|
||||
|
||||
c.CriticalOptions, err = parseTuples(g.CriticalOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Extensions, err = parseTuples(g.Extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Reserved = g.Reserved
|
||||
k, err := ParsePublicKey(g.SignatureKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.SignatureKey = k
|
||||
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
||||
if !ok || len(rest) > 0 {
|
||||
return nil, errors.New("ssh: signature parse error")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type openSSHCertSigner struct {
|
||||
pub *Certificate
|
||||
signer Signer
|
||||
}
|
||||
|
||||
// NewCertSigner returns a Signer that signs with the given Certificate, whose
|
||||
// private key is held by signer. It returns an error if the public key in cert
|
||||
// doesn't match the key used by signer.
|
||||
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
|
||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||
return nil, errors.New("ssh: signer and cert have different public key")
|
||||
}
|
||||
|
||||
return &openSSHCertSigner{cert, signer}, nil
|
||||
}
|
||||
|
||||
func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
return s.signer.Sign(rand, data)
|
||||
}
|
||||
|
||||
func (s *openSSHCertSigner) PublicKey() PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
const sourceAddressCriticalOption = "source-address"
|
||||
|
||||
// CertChecker does the work of verifying a certificate. Its methods
|
||||
// can be plugged into ClientConfig.HostKeyCallback and
|
||||
// ServerConfig.PublicKeyCallback. For the CertChecker to work,
|
||||
// minimally, the IsAuthority callback should be set.
|
||||
type CertChecker struct {
|
||||
// SupportedCriticalOptions lists the CriticalOptions that the
|
||||
// server application layer understands. These are only used
|
||||
// for user certificates.
|
||||
SupportedCriticalOptions []string
|
||||
|
||||
// IsAuthority should return true if the key is recognized as
|
||||
// an authority. This allows for certificates to be signed by other
|
||||
// certificates.
|
||||
IsAuthority func(auth PublicKey) bool
|
||||
|
||||
// Clock is used for verifying time stamps. If nil, time.Now
|
||||
// is used.
|
||||
Clock func() time.Time
|
||||
|
||||
// UserKeyFallback is called when CertChecker.Authenticate encounters a
|
||||
// public key that is not a certificate. It must implement validation
|
||||
// of user keys or else, if nil, all such keys are rejected.
|
||||
UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
|
||||
// public key that is not a certificate. It must implement host key
|
||||
// validation or else, if nil, all such keys are rejected.
|
||||
HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// IsRevoked is called for each certificate so that revocation checking
|
||||
// can be implemented. It should return true if the given certificate
|
||||
// is revoked and false otherwise. If nil, no certificates are
|
||||
// considered to have been revoked.
|
||||
IsRevoked func(cert *Certificate) bool
|
||||
}
|
||||
|
||||
// CheckHostKey checks a host key certificate. This method can be
|
||||
// plugged into ClientConfig.HostKeyCallback.
|
||||
func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error {
|
||||
cert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
if c.HostKeyFallback != nil {
|
||||
return c.HostKeyFallback(addr, remote, key)
|
||||
}
|
||||
return errors.New("ssh: non-certificate host key")
|
||||
}
|
||||
if cert.CertType != HostCert {
|
||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||
}
|
||||
|
||||
return c.CheckCert(addr, cert)
|
||||
}
|
||||
|
||||
// Authenticate checks a user certificate. Authenticate can be used as
|
||||
// a value for ServerConfig.PublicKeyCallback.
|
||||
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
||||
cert, ok := pubKey.(*Certificate)
|
||||
if !ok {
|
||||
if c.UserKeyFallback != nil {
|
||||
return c.UserKeyFallback(conn, pubKey)
|
||||
}
|
||||
return nil, errors.New("ssh: normal key pairs not accepted")
|
||||
}
|
||||
|
||||
if cert.CertType != UserCert {
|
||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||
}
|
||||
|
||||
if err := c.CheckCert(conn.User(), cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cert.Permissions, nil
|
||||
}
|
||||
|
||||
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
||||
// the signature of the certificate.
|
||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
||||
}
|
||||
|
||||
for opt, _ := range cert.CriticalOptions {
|
||||
// sourceAddressCriticalOption will be enforced by
|
||||
// serverAuthenticate
|
||||
if opt == sourceAddressCriticalOption {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, supp := range c.SupportedCriticalOptions {
|
||||
if supp == opt {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cert.ValidPrincipals) > 0 {
|
||||
// By default, certs are valid for all users/hosts.
|
||||
found := false
|
||||
for _, p := range cert.ValidPrincipals {
|
||||
if p == principal {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals)
|
||||
}
|
||||
}
|
||||
|
||||
if !c.IsAuthority(cert.SignatureKey) {
|
||||
return fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
|
||||
clock := c.Clock
|
||||
if clock == nil {
|
||||
clock = time.Now
|
||||
}
|
||||
|
||||
unixNow := clock().Unix()
|
||||
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
||||
return fmt.Errorf("ssh: cert is not yet valid")
|
||||
}
|
||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||
return fmt.Errorf("ssh: cert has expired")
|
||||
}
|
||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignCert sets c.SignatureKey to the authority's public key and stores a
|
||||
// Signature, by authority, in the certificate.
|
||||
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||
c.Nonce = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
||||
return err
|
||||
}
|
||||
c.SignatureKey = authority.PublicKey()
|
||||
|
||||
sig, err := authority.Sign(rand, c.bytesForSigning())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Signature = sig
|
||||
return nil
|
||||
}
|
||||
|
||||
var certAlgoNames = map[string]string{
|
||||
KeyAlgoRSA: CertAlgoRSAv01,
|
||||
KeyAlgoDSA: CertAlgoDSAv01,
|
||||
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
||||
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
||||
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
||||
}
|
||||
|
||||
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
||||
// Panics if a non-certificate algorithm is passed.
|
||||
func certToPrivAlgo(algo string) string {
|
||||
for privAlgo, pubAlgo := range certAlgoNames {
|
||||
if pubAlgo == algo {
|
||||
return privAlgo
|
||||
}
|
||||
}
|
||||
panic("unknown cert algorithm")
|
||||
}
|
||||
|
||||
func (cert *Certificate) bytesForSigning() []byte {
|
||||
c2 := *cert
|
||||
c2.Signature = nil
|
||||
out := c2.Marshal()
|
||||
// Drop trailing signature length.
|
||||
return out[:len(out)-4]
|
||||
}
|
||||
|
||||
// Marshal serializes c into OpenSSH's wire format. It is part of the
|
||||
// PublicKey interface.
|
||||
func (c *Certificate) Marshal() []byte {
|
||||
generic := genericCertData{
|
||||
Serial: c.Serial,
|
||||
CertType: c.CertType,
|
||||
KeyId: c.KeyId,
|
||||
ValidPrincipals: marshalStringList(c.ValidPrincipals),
|
||||
ValidAfter: uint64(c.ValidAfter),
|
||||
ValidBefore: uint64(c.ValidBefore),
|
||||
CriticalOptions: marshalTuples(c.CriticalOptions),
|
||||
Extensions: marshalTuples(c.Extensions),
|
||||
Reserved: c.Reserved,
|
||||
SignatureKey: c.SignatureKey.Marshal(),
|
||||
}
|
||||
if c.Signature != nil {
|
||||
generic.Signature = Marshal(c.Signature)
|
||||
}
|
||||
genericBytes := Marshal(&generic)
|
||||
keyBytes := c.Key.Marshal()
|
||||
_, keyBytes, _ = parseString(keyBytes)
|
||||
prefix := Marshal(&struct {
|
||||
Name string
|
||||
Nonce []byte
|
||||
Key []byte `ssh:"rest"`
|
||||
}{c.Type(), c.Nonce, keyBytes})
|
||||
|
||||
result := make([]byte, 0, len(prefix)+len(genericBytes))
|
||||
result = append(result, prefix...)
|
||||
result = append(result, genericBytes...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Type returns the key name. It is part of the PublicKey interface.
|
||||
func (c *Certificate) Type() string {
|
||||
algo, ok := certAlgoNames[c.Key.Type()]
|
||||
if !ok {
|
||||
panic("unknown cert key type")
|
||||
}
|
||||
return algo
|
||||
}
|
||||
|
||||
// Verify verifies a signature against the certificate's public
|
||||
// key. It is part of the PublicKey interface.
|
||||
func (c *Certificate) Verify(data []byte, sig *Signature) error {
|
||||
return c.Key.Verify(data, sig)
|
||||
}
|
||||
|
||||
func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||
format, in, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out = &Signature{
|
||||
Format: string(format),
|
||||
}
|
||||
|
||||
if out.Blob, in, ok = parseString(in); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
return out, in, ok
|
||||
}
|
||||
|
||||
func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||
sigBytes, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out, trailing, ok := parseSignatureBody(sigBytes)
|
||||
if !ok || len(trailing) > 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cert generated by ssh-keygen 6.0p1 Debian-4.
|
||||
// % ssh-keygen -s ca-key -I test user-key
|
||||
const exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=`
|
||||
|
||||
func TestParseCert(t *testing.T) {
|
||||
authKeyBytes := []byte(exampleSSHCert)
|
||||
|
||||
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
t.Errorf("rest: got %q, want empty", rest)
|
||||
}
|
||||
|
||||
if _, ok := key.(*Certificate); !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
|
||||
marshaled := MarshalAuthorizedKey(key)
|
||||
// Before comparison, remove the trailing newline that
|
||||
// MarshalAuthorizedKey adds.
|
||||
marshaled = marshaled[:len(marshaled)-1]
|
||||
if !bytes.Equal(authKeyBytes, marshaled) {
|
||||
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
|
||||
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
|
||||
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
|
||||
// Critical Options:
|
||||
// force-command /bin/sleep
|
||||
// source-address 192.168.1.0/24
|
||||
// Extensions:
|
||||
// permit-X11-forwarding
|
||||
// permit-agent-forwarding
|
||||
// permit-port-forwarding
|
||||
// permit-pty
|
||||
// permit-user-rc
|
||||
const exampleSSHCertWithOptions = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgDyysCJY0XrO1n03EeRRoITnTPdjENFmWDs9X58PP3VUAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMNAAAAAAAAAAAAAAABAAAACHRlc3RjZXJ0AAAAAAAAAAAAAAAA//////////8AAABLAAAADWZvcmNlLWNvbW1hbmQAAAAOAAAACi9iaW4vc2xlZXAAAAAOc291cmNlLWFkZHJlc3MAAAASAAAADjE5Mi4xNjguMS4wLzI0AAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwU+c5ui5A8+J/CFpjW8wCa52bEODA808WWQDCSuTG/eMXNf59v9Y8Pk0F1E9dGCosSNyVcB/hacUrc6He+i97+HJCyKavBsE6GDxrjRyxYqAlfcOXi/IVmaUGiO8OQ39d4GHrjToInKvExSUeleQyH4Y4/e27T/pILAqPFL3fyrvMLT5qU9QyIt6zIpa7GBP5+urouNavMprV3zsfIqNBbWypinOQAw823a5wN+zwXnhZrgQiHZ/USG09Y6k98y1dTVz8YHlQVR4D3lpTAsKDKJ5hCH9WU4fdf+lU8OyNGaJ/vz0XNqxcToe1l4numLTnaoSuH89pHryjqurB7lJKwAAAQ8AAAAHc3NoLXJzYQAAAQCaHvUIoPL1zWUHIXLvu96/HU1s/i4CAW2IIEuGgxCUCiFj6vyTyYtgxQxcmbfZf6eaITlS6XJZa7Qq4iaFZh75C1DXTX8labXhRSD4E2t//AIP9MC1rtQC5xo6FmbQ+BoKcDskr+mNACcbRSxs3IL3bwCfWDnIw2WbVox9ZdcthJKk4UoCW4ix4QwdHw7zlddlz++fGEEVhmTbll1SUkycGApPFBsAYRTMupUJcYPIeReBI/m8XfkoMk99bV8ZJQTAd7OekHY2/48Ff53jLmyDjP7kNw1F8OaPtkFs6dGJXta4krmaekPy87j+35In5hFj7yoOqvSbmYUkeX70/GGQ`
|
||||
|
||||
func TestParseCertWithOptions(t *testing.T) {
|
||||
opts := map[string]string{
|
||||
"source-address": "192.168.1.0/24",
|
||||
"force-command": "/bin/sleep",
|
||||
}
|
||||
exts := map[string]string{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
}
|
||||
authKeyBytes := []byte(exampleSSHCertWithOptions)
|
||||
|
||||
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
t.Errorf("rest: got %q, want empty", rest)
|
||||
}
|
||||
cert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
if !reflect.DeepEqual(cert.CriticalOptions, opts) {
|
||||
t.Errorf("unexpected critical options - got %v, want %v", cert.CriticalOptions, opts)
|
||||
}
|
||||
if !reflect.DeepEqual(cert.Extensions, exts) {
|
||||
t.Errorf("unexpected Extensions - got %v, want %v", cert.Extensions, exts)
|
||||
}
|
||||
marshaled := MarshalAuthorizedKey(key)
|
||||
// Before comparison, remove the trailing newline that
|
||||
// MarshalAuthorizedKey adds.
|
||||
marshaled = marshaled[:len(marshaled)-1]
|
||||
if !bytes.Equal(authKeyBytes, marshaled) {
|
||||
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCert(t *testing.T) {
|
||||
key, _, _, _, err := ParseAuthorizedKey([]byte(exampleSSHCert))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
validCert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
checker := CertChecker{}
|
||||
checker.IsAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal())
|
||||
}
|
||||
|
||||
if err := checker.CheckCert("user", validCert); err != nil {
|
||||
t.Errorf("Unable to validate certificate: %v", err)
|
||||
}
|
||||
invalidCert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
SignatureKey: testPublicKeys["ecdsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
Signature: &Signature{},
|
||||
}
|
||||
if err := checker.CheckCert("user", invalidCert); err == nil {
|
||||
t.Error("Invalid cert signature passed validation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCertTime(t *testing.T) {
|
||||
cert := Certificate{
|
||||
ValidPrincipals: []string{"user"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidAfter: 50,
|
||||
ValidBefore: 100,
|
||||
}
|
||||
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
for ts, ok := range map[int64]bool{
|
||||
25: false,
|
||||
50: true,
|
||||
99: true,
|
||||
100: false,
|
||||
125: false,
|
||||
} {
|
||||
checker := CertChecker{
|
||||
Clock: func() time.Time { return time.Unix(ts, 0) },
|
||||
}
|
||||
checker.IsAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(),
|
||||
testPublicKeys["ecdsa"].Marshal())
|
||||
}
|
||||
|
||||
if v := checker.CheckCert("user", &cert); (v == nil) != ok {
|
||||
t.Errorf("Authenticate(%d): %v", ts, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hanwen): tests for
|
||||
//
|
||||
// host keys:
|
||||
// * fallbacks
|
||||
|
||||
func TestHostKeyCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
ValidPrincipals: []string{"hostname", "hostname.domain"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: HostCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
checker := &CertChecker{
|
||||
IsAuthority: func(p PublicKey) bool {
|
||||
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||
},
|
||||
}
|
||||
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Errorf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
for _, name := range []string{"hostname", "otherhost"} {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
conf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
conf.AddHostKey(certSigner)
|
||||
_, _, _, err := NewServerConn(c1, &conf)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: checker.CheckHostKey,
|
||||
}
|
||||
_, _, _, err = NewClientConn(c2, name, config)
|
||||
|
||||
succeed := name == "hostname"
|
||||
if (err == nil) != succeed {
|
||||
t.Fatalf("NewClientConn(%q): %v", name, err)
|
||||
}
|
||||
|
||||
err = <-errc
|
||||
if (err == nil) != succeed {
|
||||
t.Fatalf("NewServerConn(%q): %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,631 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
minPacketLength = 9
|
||||
// channelMaxPacket contains the maximum number of bytes that will be
|
||||
// sent in a single packet. As per RFC 4253, section 6.1, 32k is also
|
||||
// the minimum.
|
||||
channelMaxPacket = 1 << 15
|
||||
// We follow OpenSSH here.
|
||||
channelWindowSize = 64 * channelMaxPacket
|
||||
)
|
||||
|
||||
// NewChannel represents an incoming request to a channel. It must either be
|
||||
// accepted for use by calling Accept, or rejected by calling Reject.
|
||||
type NewChannel interface {
|
||||
// Accept accepts the channel creation request. It returns the Channel
|
||||
// and a Go channel containing SSH requests. The Go channel must be
|
||||
// serviced otherwise the Channel will hang.
|
||||
Accept() (Channel, <-chan *Request, error)
|
||||
|
||||
// Reject rejects the channel creation request. After calling
|
||||
// this, no other methods on the Channel may be called.
|
||||
Reject(reason RejectionReason, message string) error
|
||||
|
||||
// ChannelType returns the type of the channel, as supplied by the
|
||||
// client.
|
||||
ChannelType() string
|
||||
|
||||
// ExtraData returns the arbitrary payload for this channel, as supplied
|
||||
// by the client. This data is specific to the channel type.
|
||||
ExtraData() []byte
|
||||
}
|
||||
|
||||
// A Channel is an ordered, reliable, flow-controlled, duplex stream
|
||||
// that is multiplexed over an SSH connection.
|
||||
type Channel interface {
|
||||
// Read reads up to len(data) bytes from the channel.
|
||||
Read(data []byte) (int, error)
|
||||
|
||||
// Write writes len(data) bytes to the channel.
|
||||
Write(data []byte) (int, error)
|
||||
|
||||
// Close signals end of channel use. No data may be sent after this
|
||||
// call.
|
||||
Close() error
|
||||
|
||||
// CloseWrite signals the end of sending in-band
|
||||
// data. Requests may still be sent, and the other side may
|
||||
// still send data
|
||||
CloseWrite() error
|
||||
|
||||
// SendRequest sends a channel request. If wantReply is true,
|
||||
// it will wait for a reply and return the result as a
|
||||
// boolean, otherwise the return value will be false. Channel
|
||||
// requests are out-of-band messages so they may be sent even
|
||||
// if the data stream is closed or blocked by flow control.
|
||||
SendRequest(name string, wantReply bool, payload []byte) (bool, error)
|
||||
|
||||
// Stderr returns an io.ReadWriter that writes to this channel
|
||||
// with the extended data type set to stderr. Stderr may
|
||||
// safely be read and written from a different goroutine than
|
||||
// Read and Write respectively.
|
||||
Stderr() io.ReadWriter
|
||||
}
|
||||
|
||||
// Request is a request sent outside of the normal stream of
|
||||
// data. Requests can either be specific to an SSH channel, or they
|
||||
// can be global.
|
||||
type Request struct {
|
||||
Type string
|
||||
WantReply bool
|
||||
Payload []byte
|
||||
|
||||
ch *channel
|
||||
mux *mux
|
||||
}
|
||||
|
||||
// Reply sends a response to a request. It must be called for all requests
|
||||
// where WantReply is true and is a no-op otherwise. The payload argument is
|
||||
// ignored for replies to channel-specific requests.
|
||||
func (r *Request) Reply(ok bool, payload []byte) error {
|
||||
if !r.WantReply {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.ch == nil {
|
||||
return r.mux.ackRequest(ok, payload)
|
||||
}
|
||||
|
||||
return r.ch.ackRequest(ok)
|
||||
}
|
||||
|
||||
// RejectionReason is an enumeration used when rejecting channel creation
|
||||
// requests. See RFC 4254, section 5.1.
|
||||
type RejectionReason uint32
|
||||
|
||||
const (
|
||||
Prohibited RejectionReason = iota + 1
|
||||
ConnectionFailed
|
||||
UnknownChannelType
|
||||
ResourceShortage
|
||||
)
|
||||
|
||||
// String converts the rejection reason to human readable form.
|
||||
func (r RejectionReason) String() string {
|
||||
switch r {
|
||||
case Prohibited:
|
||||
return "administratively prohibited"
|
||||
case ConnectionFailed:
|
||||
return "connect failed"
|
||||
case UnknownChannelType:
|
||||
return "unknown channel type"
|
||||
case ResourceShortage:
|
||||
return "resource shortage"
|
||||
}
|
||||
return fmt.Sprintf("unknown reason %d", int(r))
|
||||
}
|
||||
|
||||
func min(a uint32, b int) uint32 {
|
||||
if a < uint32(b) {
|
||||
return a
|
||||
}
|
||||
return uint32(b)
|
||||
}
|
||||
|
||||
type channelDirection uint8
|
||||
|
||||
const (
|
||||
channelInbound channelDirection = iota
|
||||
channelOutbound
|
||||
)
|
||||
|
||||
// channel is an implementation of the Channel interface that works
|
||||
// with the mux class.
|
||||
type channel struct {
|
||||
// R/O after creation
|
||||
chanType string
|
||||
extraData []byte
|
||||
localId, remoteId uint32
|
||||
|
||||
// maxIncomingPayload and maxRemotePayload are the maximum
|
||||
// payload sizes of normal and extended data packets for
|
||||
// receiving and sending, respectively. The wire packet will
|
||||
// be 9 or 13 bytes larger (excluding encryption overhead).
|
||||
maxIncomingPayload uint32
|
||||
maxRemotePayload uint32
|
||||
|
||||
mux *mux
|
||||
|
||||
// decided is set to true if an accept or reject message has been sent
|
||||
// (for outbound channels) or received (for inbound channels).
|
||||
decided bool
|
||||
|
||||
// direction contains either channelOutbound, for channels created
|
||||
// locally, or channelInbound, for channels created by the peer.
|
||||
direction channelDirection
|
||||
|
||||
// Pending internal channel messages.
|
||||
msg chan interface{}
|
||||
|
||||
// Since requests have no ID, there can be only one request
|
||||
// with WantReply=true outstanding. This lock is held by a
|
||||
// goroutine that has such an outgoing request pending.
|
||||
sentRequestMu sync.Mutex
|
||||
|
||||
incomingRequests chan *Request
|
||||
|
||||
sentEOF bool
|
||||
|
||||
// thread-safe data
|
||||
remoteWin window
|
||||
pending *buffer
|
||||
extPending *buffer
|
||||
|
||||
// windowMu protects myWindow, the flow-control window.
|
||||
windowMu sync.Mutex
|
||||
myWindow uint32
|
||||
|
||||
// writeMu serializes calls to mux.conn.writePacket() and
|
||||
// protects sentClose and packetPool. This mutex must be
|
||||
// different from windowMu, as writePacket can block if there
|
||||
// is a key exchange pending.
|
||||
writeMu sync.Mutex
|
||||
sentClose bool
|
||||
|
||||
// packetPool has a buffer for each extended channel ID to
|
||||
// save allocations during writes.
|
||||
packetPool map[uint32][]byte
|
||||
}
|
||||
|
||||
// writePacket sends a packet. If the packet is a channel close, it updates
|
||||
// sentClose. This method takes the lock c.writeMu.
|
||||
func (c *channel) writePacket(packet []byte) error {
|
||||
c.writeMu.Lock()
|
||||
if c.sentClose {
|
||||
c.writeMu.Unlock()
|
||||
return io.EOF
|
||||
}
|
||||
c.sentClose = (packet[0] == msgChannelClose)
|
||||
err := c.mux.conn.writePacket(packet)
|
||||
c.writeMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *channel) sendMessage(msg interface{}) error {
|
||||
if debugMux {
|
||||
log.Printf("send %d: %#v", c.mux.chanList.offset, msg)
|
||||
}
|
||||
|
||||
p := Marshal(msg)
|
||||
binary.BigEndian.PutUint32(p[1:], c.remoteId)
|
||||
return c.writePacket(p)
|
||||
}
|
||||
|
||||
// WriteExtended writes data to a specific extended stream. These streams are
|
||||
// used, for example, for stderr.
|
||||
func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
||||
if c.sentEOF {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
||||
opCode := byte(msgChannelData)
|
||||
headerLength := uint32(9)
|
||||
if extendedCode > 0 {
|
||||
headerLength += 4
|
||||
opCode = msgChannelExtendedData
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
packet := c.packetPool[extendedCode]
|
||||
// We don't remove the buffer from packetPool, so
|
||||
// WriteExtended calls from different goroutines will be
|
||||
// flagged as errors by the race detector.
|
||||
c.writeMu.Unlock()
|
||||
|
||||
for len(data) > 0 {
|
||||
space := min(c.maxRemotePayload, len(data))
|
||||
if space, err = c.remoteWin.reserve(space); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if want := headerLength + space; uint32(cap(packet)) < want {
|
||||
packet = make([]byte, want)
|
||||
} else {
|
||||
packet = packet[:want]
|
||||
}
|
||||
|
||||
todo := data[:space]
|
||||
|
||||
packet[0] = opCode
|
||||
binary.BigEndian.PutUint32(packet[1:], c.remoteId)
|
||||
if extendedCode > 0 {
|
||||
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
|
||||
}
|
||||
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
|
||||
copy(packet[headerLength:], todo)
|
||||
if err = c.writePacket(packet); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n += len(todo)
|
||||
data = data[len(todo):]
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
c.packetPool[extendedCode] = packet
|
||||
c.writeMu.Unlock()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) handleData(packet []byte) error {
|
||||
headerLen := 9
|
||||
isExtendedData := packet[0] == msgChannelExtendedData
|
||||
if isExtendedData {
|
||||
headerLen = 13
|
||||
}
|
||||
if len(packet) < headerLen {
|
||||
// malformed data packet
|
||||
return parseError(packet[0])
|
||||
}
|
||||
|
||||
var extended uint32
|
||||
if isExtendedData {
|
||||
extended = binary.BigEndian.Uint32(packet[5:])
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen])
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
if length > c.maxIncomingPayload {
|
||||
// TODO(hanwen): should send Disconnect?
|
||||
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
||||
}
|
||||
|
||||
data := packet[headerLen:]
|
||||
if length != uint32(len(data)) {
|
||||
return errors.New("ssh: wrong packet length")
|
||||
}
|
||||
|
||||
c.windowMu.Lock()
|
||||
if c.myWindow < length {
|
||||
c.windowMu.Unlock()
|
||||
// TODO(hanwen): should send Disconnect with reason?
|
||||
return errors.New("ssh: remote side wrote too much")
|
||||
}
|
||||
c.myWindow -= length
|
||||
c.windowMu.Unlock()
|
||||
|
||||
if extended == 1 {
|
||||
c.extPending.write(data)
|
||||
} else if extended > 0 {
|
||||
// discard other extended data.
|
||||
} else {
|
||||
c.pending.write(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) adjustWindow(n uint32) error {
|
||||
c.windowMu.Lock()
|
||||
// Since myWindow is managed on our side, and can never exceed
|
||||
// the initial window setting, we don't worry about overflow.
|
||||
c.myWindow += uint32(n)
|
||||
c.windowMu.Unlock()
|
||||
return c.sendMessage(windowAdjustMsg{
|
||||
AdditionalBytes: uint32(n),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) {
|
||||
switch extended {
|
||||
case 1:
|
||||
n, err = c.extPending.Read(data)
|
||||
case 0:
|
||||
n, err = c.pending.Read(data)
|
||||
default:
|
||||
return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended)
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
err = c.adjustWindow(uint32(n))
|
||||
// sendWindowAdjust can return io.EOF if the remote
|
||||
// peer has closed the connection, however we want to
|
||||
// defer forwarding io.EOF to the caller of Read until
|
||||
// the buffer has been drained.
|
||||
if n > 0 && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) close() {
|
||||
c.pending.eof()
|
||||
c.extPending.eof()
|
||||
close(c.msg)
|
||||
close(c.incomingRequests)
|
||||
c.writeMu.Lock()
|
||||
// This is not necesary for a normal channel teardown, but if
|
||||
// there was another error, it is.
|
||||
c.sentClose = true
|
||||
c.writeMu.Unlock()
|
||||
// Unblock writers.
|
||||
c.remoteWin.close()
|
||||
}
|
||||
|
||||
// responseMessageReceived is called when a success or failure message is
|
||||
// received on a channel to check that such a message is reasonable for the
|
||||
// given channel.
|
||||
func (c *channel) responseMessageReceived() error {
|
||||
if c.direction == channelInbound {
|
||||
return errors.New("ssh: channel response message received on inbound channel")
|
||||
}
|
||||
if c.decided {
|
||||
return errors.New("ssh: duplicate response received for channel")
|
||||
}
|
||||
c.decided = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) handlePacket(packet []byte) error {
|
||||
switch packet[0] {
|
||||
case msgChannelData, msgChannelExtendedData:
|
||||
return c.handleData(packet)
|
||||
case msgChannelClose:
|
||||
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
|
||||
c.mux.chanList.remove(c.localId)
|
||||
c.close()
|
||||
return nil
|
||||
case msgChannelEOF:
|
||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
||||
// it is logical to signal EOF at the same time.
|
||||
c.extPending.eof()
|
||||
c.pending.eof()
|
||||
return nil
|
||||
}
|
||||
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg := decoded.(type) {
|
||||
case *channelOpenFailureMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.mux.chanList.remove(msg.PeersId)
|
||||
c.msg <- msg
|
||||
case *channelOpenConfirmMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
|
||||
}
|
||||
c.remoteId = msg.MyId
|
||||
c.maxRemotePayload = msg.MaxPacketSize
|
||||
c.remoteWin.add(msg.MyWindow)
|
||||
c.msg <- msg
|
||||
case *windowAdjustMsg:
|
||||
if !c.remoteWin.add(msg.AdditionalBytes) {
|
||||
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
||||
}
|
||||
case *channelRequestMsg:
|
||||
req := Request{
|
||||
Type: msg.Request,
|
||||
WantReply: msg.WantReply,
|
||||
Payload: msg.RequestSpecificData,
|
||||
ch: c,
|
||||
}
|
||||
|
||||
c.incomingRequests <- &req
|
||||
default:
|
||||
c.msg <- msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
|
||||
ch := &channel{
|
||||
remoteWin: window{Cond: newCond()},
|
||||
myWindow: channelWindowSize,
|
||||
pending: newBuffer(),
|
||||
extPending: newBuffer(),
|
||||
direction: direction,
|
||||
incomingRequests: make(chan *Request, 16),
|
||||
msg: make(chan interface{}, 16),
|
||||
chanType: chanType,
|
||||
extraData: extraData,
|
||||
mux: m,
|
||||
packetPool: make(map[uint32][]byte),
|
||||
}
|
||||
ch.localId = m.chanList.add(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
var errUndecided = errors.New("ssh: must Accept or Reject channel")
|
||||
var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once")
|
||||
|
||||
type extChannel struct {
|
||||
code uint32
|
||||
ch *channel
|
||||
}
|
||||
|
||||
func (e *extChannel) Write(data []byte) (n int, err error) {
|
||||
return e.ch.WriteExtended(data, e.code)
|
||||
}
|
||||
|
||||
func (e *extChannel) Read(data []byte) (n int, err error) {
|
||||
return e.ch.ReadExtended(data, e.code)
|
||||
}
|
||||
|
||||
func (c *channel) Accept() (Channel, <-chan *Request, error) {
|
||||
if c.decided {
|
||||
return nil, nil, errDecidedAlready
|
||||
}
|
||||
c.maxIncomingPayload = channelMaxPacket
|
||||
confirm := channelOpenConfirmMsg{
|
||||
PeersId: c.remoteId,
|
||||
MyId: c.localId,
|
||||
MyWindow: c.myWindow,
|
||||
MaxPacketSize: c.maxIncomingPayload,
|
||||
}
|
||||
c.decided = true
|
||||
if err := c.sendMessage(confirm); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c, c.incomingRequests, nil
|
||||
}
|
||||
|
||||
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||
if ch.decided {
|
||||
return errDecidedAlready
|
||||
}
|
||||
reject := channelOpenFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
Language: "en",
|
||||
}
|
||||
ch.decided = true
|
||||
return ch.sendMessage(reject)
|
||||
}
|
||||
|
||||
func (ch *channel) Read(data []byte) (int, error) {
|
||||
if !ch.decided {
|
||||
return 0, errUndecided
|
||||
}
|
||||
return ch.ReadExtended(data, 0)
|
||||
}
|
||||
|
||||
func (ch *channel) Write(data []byte) (int, error) {
|
||||
if !ch.decided {
|
||||
return 0, errUndecided
|
||||
}
|
||||
return ch.WriteExtended(data, 0)
|
||||
}
|
||||
|
||||
func (ch *channel) CloseWrite() error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
ch.sentEOF = true
|
||||
return ch.sendMessage(channelEOFMsg{
|
||||
PeersId: ch.remoteId})
|
||||
}
|
||||
|
||||
func (ch *channel) Close() error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
|
||||
return ch.sendMessage(channelCloseMsg{
|
||||
PeersId: ch.remoteId})
|
||||
}
|
||||
|
||||
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
||||
// SSH extended stream. Such streams are used, for example, for stderr.
|
||||
func (ch *channel) Extended(code uint32) io.ReadWriter {
|
||||
if !ch.decided {
|
||||
return nil
|
||||
}
|
||||
return &extChannel{code, ch}
|
||||
}
|
||||
|
||||
func (ch *channel) Stderr() io.ReadWriter {
|
||||
return ch.Extended(1)
|
||||
}
|
||||
|
||||
func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
|
||||
if !ch.decided {
|
||||
return false, errUndecided
|
||||
}
|
||||
|
||||
if wantReply {
|
||||
ch.sentRequestMu.Lock()
|
||||
defer ch.sentRequestMu.Unlock()
|
||||
}
|
||||
|
||||
msg := channelRequestMsg{
|
||||
PeersId: ch.remoteId,
|
||||
Request: name,
|
||||
WantReply: wantReply,
|
||||
RequestSpecificData: payload,
|
||||
}
|
||||
|
||||
if err := ch.sendMessage(msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if wantReply {
|
||||
m, ok := (<-ch.msg)
|
||||
if !ok {
|
||||
return false, io.EOF
|
||||
}
|
||||
switch m.(type) {
|
||||
case *channelRequestFailureMsg:
|
||||
return false, nil
|
||||
case *channelRequestSuccessMsg:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ackRequest either sends an ack or nack to the channel request.
|
||||
func (ch *channel) ackRequest(ok bool) error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
|
||||
var msg interface{}
|
||||
if !ok {
|
||||
msg = channelRequestFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
}
|
||||
} else {
|
||||
msg = channelRequestSuccessMsg{
|
||||
PeersId: ch.remoteId,
|
||||
}
|
||||
}
|
||||
return ch.sendMessage(msg)
|
||||
}
|
||||
|
||||
func (ch *channel) ChannelType() string {
|
||||
return ch.chanType
|
||||
}
|
||||
|
||||
func (ch *channel) ExtraData() []byte {
|
||||
return ch.extraData
|
||||
}
|
||||
@@ -1,549 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rc4"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const (
|
||||
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
||||
|
||||
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
|
||||
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
|
||||
// indicates implementations SHOULD be able to handle larger packet sizes, but then
|
||||
// waffles on about reasonable limits.
|
||||
//
|
||||
// OpenSSH caps their maxPacket at 256kB so we choose to do
|
||||
// the same. maxPacket is also used to ensure that uint32
|
||||
// length fields do not overflow, so it should remain well
|
||||
// below 4G.
|
||||
maxPacket = 256 * 1024
|
||||
)
|
||||
|
||||
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
||||
// by the transport before the first key-exchange.
|
||||
type noneCipher struct{}
|
||||
|
||||
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
||||
copy(dst, src)
|
||||
}
|
||||
|
||||
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(c, iv), nil
|
||||
}
|
||||
|
||||
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
||||
return rc4.NewCipher(key)
|
||||
}
|
||||
|
||||
type streamCipherMode struct {
|
||||
keySize int
|
||||
ivSize int
|
||||
skip int
|
||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
||||
}
|
||||
|
||||
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
||||
if len(key) < c.keySize {
|
||||
panic("ssh: key length too small for cipher")
|
||||
}
|
||||
if len(iv) < c.ivSize {
|
||||
panic("ssh: iv too small for cipher")
|
||||
}
|
||||
|
||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var streamDump []byte
|
||||
if c.skip > 0 {
|
||||
streamDump = make([]byte, 512)
|
||||
}
|
||||
|
||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
||||
dumpThisTime := remainingToDump
|
||||
if dumpThisTime > len(streamDump) {
|
||||
dumpThisTime = len(streamDump)
|
||||
}
|
||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
||||
remainingToDump -= dumpThisTime
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||
// are not supported and will not be negotiated, even if explicitly requested in
|
||||
// ClientConfig.Crypto.Ciphers.
|
||||
var cipherModes = map[string]*streamCipherMode{
|
||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
||||
// are defined in the order specified in the RFC.
|
||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
||||
|
||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
||||
// They are defined in the order specified in the RFC.
|
||||
"arcfour128": {16, 0, 1536, newRC4},
|
||||
"arcfour256": {32, 0, 1536, newRC4},
|
||||
|
||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
||||
// RC4) has problems with weak keys, and should be used with caution."
|
||||
// RFC4345 introduces improved versions of Arcfour.
|
||||
"arcfour": {16, 0, 0, newRC4},
|
||||
|
||||
// AES-GCM is not a stream cipher, so it is constructed with a
|
||||
// special case. If we add any more non-stream ciphers, we
|
||||
// should invest a cleaner way to do this.
|
||||
gcmCipherID: {16, 12, 0, nil},
|
||||
|
||||
// insecure cipher, see http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf
|
||||
// uncomment below to enable it.
|
||||
// aes128cbcID: {16, aes.BlockSize, 0, nil},
|
||||
}
|
||||
|
||||
// prefixLen is the length of the packet prefix that contains the packet length
|
||||
// and number of padding bytes.
|
||||
const prefixLen = 5
|
||||
|
||||
// streamPacketCipher is a packetCipher using a stream cipher.
|
||||
type streamPacketCipher struct {
|
||||
mac hash.Hash
|
||||
cipher cipher.Stream
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
prefix [prefixLen]byte
|
||||
seqNumBytes [4]byte
|
||||
padding [2 * packetSizeMultiple]byte
|
||||
packetData []byte
|
||||
macResult []byte
|
||||
}
|
||||
|
||||
// readPacket reads and decrypt a single packet from the reader argument.
|
||||
func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
if _, err := io.ReadFull(r, s.prefix[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
length := binary.BigEndian.Uint32(s.prefix[0:4])
|
||||
paddingLength := uint32(s.prefix[4])
|
||||
|
||||
var macSize uint32
|
||||
if s.mac != nil {
|
||||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
s.mac.Write(s.prefix[:])
|
||||
macSize = uint32(s.mac.Size())
|
||||
}
|
||||
|
||||
if length <= paddingLength+1 {
|
||||
return nil, errors.New("ssh: invalid packet length, packet too small")
|
||||
}
|
||||
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: invalid packet length, packet too large")
|
||||
}
|
||||
|
||||
// the maxPacket check above ensures that length-1+macSize
|
||||
// does not overflow.
|
||||
if uint32(cap(s.packetData)) < length-1+macSize {
|
||||
s.packetData = make([]byte, length-1+macSize)
|
||||
} else {
|
||||
s.packetData = s.packetData[:length-1+macSize]
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, s.packetData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := s.packetData[length-1:]
|
||||
data := s.packetData[:length-1]
|
||||
s.cipher.XORKeyStream(data, data)
|
||||
|
||||
if s.mac != nil {
|
||||
s.mac.Write(data)
|
||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
||||
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
|
||||
return nil, errors.New("ssh: MAC failure")
|
||||
}
|
||||
}
|
||||
|
||||
return s.packetData[:length-paddingLength-1], nil
|
||||
}
|
||||
|
||||
// writePacket encrypts and sends a packet of data to the writer argument
|
||||
func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
if len(packet) > maxPacket {
|
||||
return errors.New("ssh: packet too large")
|
||||
}
|
||||
|
||||
paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple
|
||||
if paddingLength < 4 {
|
||||
paddingLength += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := len(packet) + 1 + paddingLength
|
||||
binary.BigEndian.PutUint32(s.prefix[:], uint32(length))
|
||||
s.prefix[4] = byte(paddingLength)
|
||||
padding := s.padding[:paddingLength]
|
||||
if _, err := io.ReadFull(rand, padding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.mac != nil {
|
||||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
s.mac.Write(s.prefix[:])
|
||||
s.mac.Write(packet)
|
||||
s.mac.Write(padding)
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
s.cipher.XORKeyStream(packet, packet)
|
||||
s.cipher.XORKeyStream(padding, padding)
|
||||
|
||||
if _, err := w.Write(s.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(packet); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(padding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.mac != nil {
|
||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
||||
if _, err := w.Write(s.macResult); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type gcmCipher struct {
|
||||
aead cipher.AEAD
|
||||
prefix [4]byte
|
||||
iv []byte
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gcmCipher{
|
||||
aead: aead,
|
||||
iv: iv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const gcmTagSize = 16
|
||||
|
||||
func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
// Pad out to multiple of 16 bytes. This is different from the
|
||||
// stream cipher because that encrypts the length too.
|
||||
padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple)
|
||||
if padding < 4 {
|
||||
padding += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := uint32(len(packet) + int(padding) + 1)
|
||||
binary.BigEndian.PutUint32(c.prefix[:], length)
|
||||
if _, err := w.Write(c.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length) {
|
||||
c.buf = make([]byte, length)
|
||||
} else {
|
||||
c.buf = c.buf[:length]
|
||||
}
|
||||
|
||||
c.buf[0] = padding
|
||||
copy(c.buf[1:], packet)
|
||||
if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil {
|
||||
return err
|
||||
}
|
||||
c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
||||
if _, err := w.Write(c.buf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.incIV()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *gcmCipher) incIV() {
|
||||
for i := 4 + 7; i >= 4; i-- {
|
||||
c.iv[i]++
|
||||
if c.iv[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
if _, err := io.ReadFull(r, c.prefix[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(c.prefix[:])
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: max packet length exceeded.")
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length+gcmTagSize) {
|
||||
c.buf = make([]byte, length+gcmTagSize)
|
||||
} else {
|
||||
c.buf = c.buf[:length+gcmTagSize]
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, c.buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.incIV()
|
||||
|
||||
padding := plain[0]
|
||||
if padding < 4 || padding >= 20 {
|
||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||
}
|
||||
|
||||
if int(padding+1) >= len(plain) {
|
||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||
}
|
||||
plain = plain[1 : length-uint32(padding)]
|
||||
return plain, nil
|
||||
}
|
||||
|
||||
// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1
|
||||
type cbcCipher struct {
|
||||
mac hash.Hash
|
||||
macSize uint32
|
||||
decrypter cipher.BlockMode
|
||||
encrypter cipher.BlockMode
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
seqNumBytes [4]byte
|
||||
packetData []byte
|
||||
macResult []byte
|
||||
|
||||
// Amount of data we should still read to hide which
|
||||
// verification error triggered.
|
||||
oracleCamouflage uint32
|
||||
}
|
||||
|
||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cbc := &cbcCipher{
|
||||
mac: macModes[algs.MAC].new(macKey),
|
||||
decrypter: cipher.NewCBCDecrypter(c, iv),
|
||||
encrypter: cipher.NewCBCEncrypter(c, iv),
|
||||
packetData: make([]byte, 1024),
|
||||
}
|
||||
if cbc.mac != nil {
|
||||
cbc.macSize = uint32(cbc.mac.Size())
|
||||
}
|
||||
|
||||
return cbc, nil
|
||||
}
|
||||
|
||||
func maxUInt32(a, b int) uint32 {
|
||||
if a > b {
|
||||
return uint32(a)
|
||||
}
|
||||
return uint32(b)
|
||||
}
|
||||
|
||||
const (
|
||||
cbcMinPacketSizeMultiple = 8
|
||||
cbcMinPacketSize = 16
|
||||
cbcMinPaddingSize = 4
|
||||
)
|
||||
|
||||
// cbcError represents a verification error that may leak information.
|
||||
type cbcError string
|
||||
|
||||
func (e cbcError) Error() string { return string(e) }
|
||||
|
||||
func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
p, err := c.readPacketLeaky(seqNum, r)
|
||||
if err != nil {
|
||||
if _, ok := err.(cbcError); ok {
|
||||
// Verification error: read a fixed amount of
|
||||
// data, to make distinguishing between
|
||||
// failing MAC and failing length check more
|
||||
// difficult.
|
||||
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
|
||||
}
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
blockSize := c.decrypter.BlockSize()
|
||||
|
||||
// Read the header, which will include some of the subsequent data in the
|
||||
// case of block ciphers - this is copied back to the payload later.
|
||||
// How many bytes of payload/padding will be read with this first read.
|
||||
firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize)
|
||||
firstBlock := c.packetData[:firstBlockLength]
|
||||
if _, err := io.ReadFull(r, firstBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength
|
||||
|
||||
c.decrypter.CryptBlocks(firstBlock, firstBlock)
|
||||
length := binary.BigEndian.Uint32(firstBlock[:4])
|
||||
if length > maxPacket {
|
||||
return nil, cbcError("ssh: packet too large")
|
||||
}
|
||||
if length+4 < maxUInt32(cbcMinPacketSize, blockSize) {
|
||||
// The minimum size of a packet is 16 (or the cipher block size, whichever
|
||||
// is larger) bytes.
|
||||
return nil, cbcError("ssh: packet too small")
|
||||
}
|
||||
// The length of the packet (including the length field but not the MAC) must
|
||||
// be a multiple of the block size or 8, whichever is larger.
|
||||
if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 {
|
||||
return nil, cbcError("ssh: invalid packet length multiple")
|
||||
}
|
||||
|
||||
paddingLength := uint32(firstBlock[4])
|
||||
if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 {
|
||||
return nil, cbcError("ssh: invalid packet length")
|
||||
}
|
||||
|
||||
// Positions within the c.packetData buffer:
|
||||
macStart := 4 + length
|
||||
paddingStart := macStart - paddingLength
|
||||
|
||||
// Entire packet size, starting before length, ending at end of mac.
|
||||
entirePacketSize := macStart + c.macSize
|
||||
|
||||
// Ensure c.packetData is large enough for the entire packet data.
|
||||
if uint32(cap(c.packetData)) < entirePacketSize {
|
||||
// Still need to upsize and copy, but this should be rare at runtime, only
|
||||
// on upsizing the packetData buffer.
|
||||
c.packetData = make([]byte, entirePacketSize)
|
||||
copy(c.packetData, firstBlock)
|
||||
} else {
|
||||
c.packetData = c.packetData[:entirePacketSize]
|
||||
}
|
||||
|
||||
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.oracleCamouflage -= uint32(n)
|
||||
}
|
||||
|
||||
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
||||
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
||||
|
||||
mac := c.packetData[macStart:]
|
||||
if c.mac != nil {
|
||||
c.mac.Reset()
|
||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
||||
c.mac.Write(c.seqNumBytes[:])
|
||||
c.mac.Write(c.packetData[:macStart])
|
||||
c.macResult = c.mac.Sum(c.macResult[:0])
|
||||
if subtle.ConstantTimeCompare(c.macResult, mac) != 1 {
|
||||
return nil, cbcError("ssh: MAC failure")
|
||||
}
|
||||
}
|
||||
|
||||
return c.packetData[prefixLen:paddingStart], nil
|
||||
}
|
||||
|
||||
func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize())
|
||||
|
||||
// Length of encrypted portion of the packet (header, payload, padding).
|
||||
// Enforce minimum padding and packet size.
|
||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
||||
// Enforce block size.
|
||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
||||
|
||||
length := encLength - 4
|
||||
paddingLength := int(length) - (1 + len(packet))
|
||||
|
||||
// Overall buffer contains: header, payload, padding, mac.
|
||||
// Space for the MAC is reserved in the capacity but not the slice length.
|
||||
bufferSize := encLength + c.macSize
|
||||
if uint32(cap(c.packetData)) < bufferSize {
|
||||
c.packetData = make([]byte, encLength, bufferSize)
|
||||
} else {
|
||||
c.packetData = c.packetData[:encLength]
|
||||
}
|
||||
|
||||
p := c.packetData
|
||||
|
||||
// Packet header.
|
||||
binary.BigEndian.PutUint32(p, length)
|
||||
p = p[4:]
|
||||
p[0] = byte(paddingLength)
|
||||
|
||||
// Payload.
|
||||
p = p[1:]
|
||||
copy(p, packet)
|
||||
|
||||
// Padding.
|
||||
p = p[len(packet):]
|
||||
if _, err := io.ReadFull(rand, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.mac != nil {
|
||||
c.mac.Reset()
|
||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
||||
c.mac.Write(c.seqNumBytes[:])
|
||||
c.mac.Write(c.packetData)
|
||||
// The MAC is now appended into the capacity reserved for it earlier.
|
||||
c.packetData = c.mac.Sum(c.packetData)
|
||||
}
|
||||
|
||||
c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength])
|
||||
|
||||
if _, err := w.Write(c.packetData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultCiphersExist(t *testing.T) {
|
||||
for _, cipherAlgo := range supportedCiphers {
|
||||
if _, ok := cipherModes[cipherAlgo]; !ok {
|
||||
t.Errorf("default cipher %q is unknown", cipherAlgo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketCiphers(t *testing.T) {
|
||||
// Still test aes128cbc cipher althought it's commented out.
|
||||
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
|
||||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
for cipher := range cipherModes {
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: cipher,
|
||||
MAC: "hmac-sha1",
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket(%q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
|
||||
packet, err := server.readPacket(0, buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPacket(%q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(packet) != want {
|
||||
t.Errorf("roundtrip(%q): got %q, want %q", cipher, packet, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCBCOracleCounterMeasure(t *testing.T) {
|
||||
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
|
||||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: aes128cbcID,
|
||||
MAC: "hmac-sha1",
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Fatalf("newPacketCipher(client): %v", err)
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
packetSize := buf.Len()
|
||||
buf.Write(make([]byte, 2*maxPacket))
|
||||
|
||||
// We corrupt each byte, but this usually will only test the
|
||||
// 'packet too large' or 'MAC failure' cases.
|
||||
lastRead := -1
|
||||
for i := 0; i < packetSize; i++ {
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Fatalf("newPacketCipher(client): %v", err)
|
||||
}
|
||||
|
||||
fresh := &bytes.Buffer{}
|
||||
fresh.Write(buf.Bytes())
|
||||
fresh.Bytes()[i] ^= 0x01
|
||||
|
||||
before := fresh.Len()
|
||||
_, err = server.readPacket(0, fresh)
|
||||
if err == nil {
|
||||
t.Errorf("corrupt byte %d: readPacket succeeded ", i)
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(cbcError); !ok {
|
||||
t.Errorf("corrupt byte %d: got %v (%T), want cbcError", i, err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
after := fresh.Len()
|
||||
bytesRead := before - after
|
||||
if bytesRead < maxPacket {
|
||||
t.Errorf("corrupt byte %d: read %d bytes, want more than %d", i, bytesRead, maxPacket)
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 && bytesRead != lastRead {
|
||||
t.Errorf("corrupt byte %d: read %d bytes, want %d bytes read", i, bytesRead, lastRead)
|
||||
}
|
||||
lastRead = bytesRead
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Client implements a traditional SSH client that supports shells,
|
||||
// subprocesses, port forwarding and tunneled dialing.
|
||||
type Client struct {
|
||||
Conn
|
||||
|
||||
forwards forwardList // forwarded tcpip connections from the remote side
|
||||
mu sync.Mutex
|
||||
channelHandlers map[string]chan NewChannel
|
||||
}
|
||||
|
||||
// HandleChannelOpen returns a channel on which NewChannel requests
|
||||
// for the given type are sent. If the type already is being handled,
|
||||
// nil is returned. The channel is closed when the connection is closed.
|
||||
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.channelHandlers == nil {
|
||||
// The SSH channel has been closed.
|
||||
c := make(chan NewChannel)
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
|
||||
ch := c.channelHandlers[channelType]
|
||||
if ch != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ch = make(chan NewChannel, 16)
|
||||
c.channelHandlers[channelType] = ch
|
||||
return ch
|
||||
}
|
||||
|
||||
// NewClient creates a Client on top of the given connection.
|
||||
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
||||
conn := &Client{
|
||||
Conn: c,
|
||||
channelHandlers: make(map[string]chan NewChannel, 1),
|
||||
}
|
||||
|
||||
go conn.handleGlobalRequests(reqs)
|
||||
go conn.handleChannelOpens(chans)
|
||||
go func() {
|
||||
conn.Wait()
|
||||
conn.forwards.closeAll()
|
||||
}()
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
||||
return conn
|
||||
}
|
||||
|
||||
// NewClientConn establishes an authenticated SSH connection using c
|
||||
// as the underlying transport. The Request and NewChannel channels
|
||||
// must be serviced or the connection will hang.
|
||||
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
conn := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
||||
if err := conn.clientHandshake(addr, &fullConf); err != nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
|
||||
}
|
||||
conn.mux = newMux(conn.transport)
|
||||
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
||||
}
|
||||
|
||||
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
||||
// 7.
|
||||
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
||||
if config.ClientVersion != "" {
|
||||
c.clientVersion = []byte(config.ClientVersion)
|
||||
} else {
|
||||
c.clientVersion = []byte(packageVersion)
|
||||
}
|
||||
var err error
|
||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
||||
if err := c.transport.requestKeyChange(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if packet, err := c.transport.readPacket(); err != nil {
|
||||
return err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
|
||||
// We just did the key change, so the session ID is established.
|
||||
c.sessionID = c.transport.getSessionID()
|
||||
|
||||
return c.clientAuthenticate(config)
|
||||
}
|
||||
|
||||
// verifyHostKeySignature verifies the host key obtained in the key
|
||||
// exchange.
|
||||
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
|
||||
sig, rest, ok := parseSignatureBody(result.Signature)
|
||||
if len(rest) > 0 || !ok {
|
||||
return errors.New("ssh: signature parse error")
|
||||
}
|
||||
|
||||
return hostKey.Verify(result.H, sig)
|
||||
}
|
||||
|
||||
// NewSession opens a new Session for this client. (A session is a remote
|
||||
// execution of a program.)
|
||||
func (c *Client) NewSession() (*Session, error) {
|
||||
ch, in, err := c.OpenChannel("session", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSession(ch, in)
|
||||
}
|
||||
|
||||
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
|
||||
for r := range incoming {
|
||||
// This handles keepalive messages and matches
|
||||
// the behaviour of OpenSSH.
|
||||
r.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// handleChannelOpens channel open messages from the remote side.
|
||||
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
c.mu.Lock()
|
||||
handler := c.channelHandlers[ch.ChannelType()]
|
||||
c.mu.Unlock()
|
||||
|
||||
if handler != nil {
|
||||
handler <- ch
|
||||
} else {
|
||||
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
for _, ch := range c.channelHandlers {
|
||||
close(ch)
|
||||
}
|
||||
c.channelHandlers = nil
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Dial starts a client connection to the given SSH server. It is a
|
||||
// convenience function that connects to the given network address,
|
||||
// initiates the SSH handshake, and then sets up a Client. For access
|
||||
// to incoming channels and requests, use net.Dial with NewClientConn
|
||||
// instead.
|
||||
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
||||
conn, err := net.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, chans, reqs, err := NewClientConn(conn, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(c, chans, reqs), nil
|
||||
}
|
||||
|
||||
// A ClientConfig structure is used to configure a Client. It must not be
|
||||
// modified after having been passed to an SSH function.
|
||||
type ClientConfig struct {
|
||||
// Config contains configuration that is shared between clients and
|
||||
// servers.
|
||||
Config
|
||||
|
||||
// User contains the username to authenticate as.
|
||||
User string
|
||||
|
||||
// Auth contains possible authentication methods to use with the
|
||||
// server. Only the first instance of a particular RFC 4252 method will
|
||||
// be used during authentication.
|
||||
Auth []AuthMethod
|
||||
|
||||
// HostKeyCallback, if not nil, is called during the cryptographic
|
||||
// handshake to validate the server's host key. A nil HostKeyCallback
|
||||
// implies that all host keys are accepted.
|
||||
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// ClientVersion contains the version identification string that will
|
||||
// be used for the connection. If empty, a reasonable default is used.
|
||||
ClientVersion string
|
||||
|
||||
// HostKeyAlgorithms lists the key types that the client will
|
||||
// accept from the server as host key, in order of
|
||||
// preference. If empty, a reasonable default is used. Any
|
||||
// string returned from PublicKey.Type method may be used, or
|
||||
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
|
||||
HostKeyAlgorithms []string
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
// initiate user auth session
|
||||
if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
|
||||
return err
|
||||
}
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var serviceAccept serviceAcceptMsg
|
||||
if err := Unmarshal(packet, &serviceAccept); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// during the authentication phase the client first attempts the "none" method
|
||||
// then any untried methods suggested by the server.
|
||||
tried := make(map[string]bool)
|
||||
var lastMethods []string
|
||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
||||
ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
// success
|
||||
return nil
|
||||
}
|
||||
tried[auth.method()] = true
|
||||
if methods == nil {
|
||||
methods = lastMethods
|
||||
}
|
||||
lastMethods = methods
|
||||
|
||||
auth = nil
|
||||
|
||||
findNext:
|
||||
for _, a := range config.Auth {
|
||||
candidateMethod := a.method()
|
||||
if tried[candidateMethod] {
|
||||
continue
|
||||
}
|
||||
for _, meth := range methods {
|
||||
if meth == candidateMethod {
|
||||
auth = a
|
||||
break findNext
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
|
||||
}
|
||||
|
||||
func keys(m map[string]bool) []string {
|
||||
s := make([]string, 0, len(m))
|
||||
|
||||
for key := range m {
|
||||
s = append(s, key)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
||||
type AuthMethod interface {
|
||||
// auth authenticates user over transport t.
|
||||
// Returns true if authentication is successful.
|
||||
// If authentication is not successful, a []string of alternative
|
||||
// method names is returned. If the slice is nil, it will be ignored
|
||||
// and the previous set of possible methods will be reused.
|
||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
||||
|
||||
// method returns the RFC 4252 method name.
|
||||
method() string
|
||||
}
|
||||
|
||||
// "none" authentication, RFC 4252 section 5.2.
|
||||
type noneAuth int
|
||||
|
||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "none",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return handleAuthResponse(c)
|
||||
}
|
||||
|
||||
func (n *noneAuth) method() string {
|
||||
return "none"
|
||||
}
|
||||
|
||||
// passwordCallback is an AuthMethod that fetches the password through
|
||||
// a function call, e.g. by prompting the user.
|
||||
type passwordCallback func() (password string, err error)
|
||||
|
||||
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
type passwordAuthMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Reply bool
|
||||
Password string
|
||||
}
|
||||
|
||||
pw, err := cb()
|
||||
// REVIEW NOTE: is there a need to support skipping a password attempt?
|
||||
// The program may only find out that the user doesn't have a password
|
||||
// when prompting.
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
Reply: false,
|
||||
Password: pw,
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return handleAuthResponse(c)
|
||||
}
|
||||
|
||||
func (cb passwordCallback) method() string {
|
||||
return "password"
|
||||
}
|
||||
|
||||
// Password returns an AuthMethod using the given password.
|
||||
func Password(secret string) AuthMethod {
|
||||
return passwordCallback(func() (string, error) { return secret, nil })
|
||||
}
|
||||
|
||||
// PasswordCallback returns an AuthMethod that uses a callback for
|
||||
// fetching a password.
|
||||
func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
|
||||
return passwordCallback(prompt)
|
||||
}
|
||||
|
||||
type publickeyAuthMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
// HasSig indicates to the receiver packet that the auth request is signed and
|
||||
// should be used for authentication of the request.
|
||||
HasSig bool
|
||||
Algoname string
|
||||
PubKey []byte
|
||||
// Sig is tagged with "rest" so Marshal will exclude it during
|
||||
// validateKey
|
||||
Sig []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// publicKeyCallback is an AuthMethod that uses a set of key
|
||||
// pairs for authentication.
|
||||
type publicKeyCallback func() ([]Signer, error)
|
||||
|
||||
func (cb publicKeyCallback) method() string {
|
||||
return "publickey"
|
||||
}
|
||||
|
||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
// Authentication is performed in two stages. The first stage sends an
|
||||
// enquiry to test if each key is acceptable to the remote. The second
|
||||
// stage attempts to authenticate with the valid keys obtained in the
|
||||
// first stage.
|
||||
|
||||
signers, err := cb()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var validKeys []Signer
|
||||
for _, signer := range signers {
|
||||
if ok, err := validateKey(signer.PublicKey(), user, c); ok {
|
||||
validKeys = append(validKeys, signer)
|
||||
} else {
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// methods that may continue if this auth is not successful.
|
||||
var methods []string
|
||||
for _, signer := range validKeys {
|
||||
pub := signer.PublicKey()
|
||||
|
||||
pubKey := pub.Marshal()
|
||||
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
}, []byte(pub.Type()), pubKey))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// manually wrap the serialized signature in a string
|
||||
s := Marshal(sign)
|
||||
sig := make([]byte, stringLength(len(s)))
|
||||
marshalString(sig, s)
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
HasSig: true,
|
||||
Algoname: pub.Type(),
|
||||
PubKey: pubKey,
|
||||
Sig: sig,
|
||||
}
|
||||
p := Marshal(&msg)
|
||||
if err := c.writePacket(p); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var success bool
|
||||
success, methods, err = handleAuthResponse(c)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if success {
|
||||
return success, methods, err
|
||||
}
|
||||
}
|
||||
return false, methods, nil
|
||||
}
|
||||
|
||||
// validateKey validates the key provided is acceptable to the server.
|
||||
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "publickey",
|
||||
HasSig: false,
|
||||
Algoname: key.Type(),
|
||||
PubKey: pubKey,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&msg)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return confirmKeyAck(key, c)
|
||||
}
|
||||
|
||||
func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
algoname := key.Type()
|
||||
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO(gpaul): add callback to present the banner to the user
|
||||
case msgUserAuthPubKeyOk:
|
||||
var msg userAuthPubKeyOkMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
case msgUserAuthFailure:
|
||||
return false, nil
|
||||
default:
|
||||
return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeys returns an AuthMethod that uses the given key
|
||||
// pairs.
|
||||
func PublicKeys(signers ...Signer) AuthMethod {
|
||||
return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
|
||||
}
|
||||
|
||||
// PublicKeysCallback returns an AuthMethod that runs the given
|
||||
// function to obtain a list of key pairs.
|
||||
func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
|
||||
return publicKeyCallback(getSigners)
|
||||
}
|
||||
|
||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
||||
// along with a list of remaining authentication methods to try next and
|
||||
// an error if an unexpected response was received.
|
||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: add callback to present the banner to the user
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
case msgDisconnect:
|
||||
return false, nil, io.EOF
|
||||
default:
|
||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KeyboardInteractiveChallenge should print questions, optionally
|
||||
// disabling echoing (e.g. for passwords), and return all the answers.
|
||||
// Challenge may be called multiple times in a single session. After
|
||||
// successful authentication, the server may send a challenge with no
|
||||
// questions, for which the user and instruction messages should be
|
||||
// printed. RFC 4256 section 3.3 details how the UI should behave for
|
||||
// both CLI and GUI environments.
|
||||
type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
|
||||
|
||||
// KeyboardInteractive returns a AuthMethod using a prompt/response
|
||||
// sequence controlled by the server.
|
||||
func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
|
||||
return challenge
|
||||
}
|
||||
|
||||
func (cb KeyboardInteractiveChallenge) method() string {
|
||||
return "keyboard-interactive"
|
||||
}
|
||||
|
||||
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
type initiateMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Language string
|
||||
Submethods string
|
||||
}
|
||||
|
||||
if err := c.writePacket(Marshal(&initiateMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "keyboard-interactive",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// like handleAuthResponse, but with less options.
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: Print banners during userauth.
|
||||
continue
|
||||
case msgUserAuthInfoRequest:
|
||||
// OK
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
default:
|
||||
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
||||
}
|
||||
|
||||
var msg userAuthInfoRequestMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// Manually unpack the prompt/echo pairs.
|
||||
rest := msg.Prompts
|
||||
var prompts []string
|
||||
var echos []bool
|
||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
||||
prompt, r, ok := parseString(rest)
|
||||
if !ok || len(r) == 0 {
|
||||
return false, nil, errors.New("ssh: prompt format error")
|
||||
}
|
||||
prompts = append(prompts, string(prompt))
|
||||
echos = append(echos, r[0] != 0)
|
||||
rest = r[1:]
|
||||
}
|
||||
|
||||
if len(rest) != 0 {
|
||||
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
||||
}
|
||||
|
||||
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if len(answers) != len(prompts) {
|
||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
||||
}
|
||||
responseLength := 1 + 4
|
||||
for _, a := range answers {
|
||||
responseLength += stringLength(len(a))
|
||||
}
|
||||
serialized := make([]byte, responseLength)
|
||||
p := serialized
|
||||
p[0] = msgUserAuthInfoResponse
|
||||
p = p[1:]
|
||||
p = marshalUint32(p, uint32(len(answers)))
|
||||
for _, a := range answers {
|
||||
p = marshalString(p, []byte(a))
|
||||
}
|
||||
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type keyboardInteractive map[string]string
|
||||
|
||||
func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
var answers []string
|
||||
for _, q := range questions {
|
||||
answers = append(answers, cr[q])
|
||||
}
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
// reused internally by tests
|
||||
var clientPassword = "tiger"
|
||||
|
||||
// tryAuth runs a handshake with a given config against an SSH server
|
||||
// with config serverConfig
|
||||
func tryAuth(t *testing.T, config *ClientConfig) error {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
certChecker := CertChecker{
|
||||
IsAuthority: func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal())
|
||||
},
|
||||
UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
|
||||
},
|
||||
IsRevoked: func(c *Certificate) bool {
|
||||
return c.Serial == 666
|
||||
},
|
||||
}
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && string(pass) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("password auth failed")
|
||||
},
|
||||
PublicKeyCallback: certChecker.Authenticate,
|
||||
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
|
||||
ans, err := challenge("user",
|
||||
"instruction",
|
||||
[]string{"question1", "question2"},
|
||||
[]bool{true, true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2"
|
||||
if ok {
|
||||
challenge("user", "motd", nil, nil)
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("keyboard-interactive failed")
|
||||
},
|
||||
AuthLogCallback: func(conn ConnMetadata, method string, err error) {
|
||||
t.Logf("user %q, method %q: %v", conn.User(), method, err)
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
go newServer(c1, serverConfig)
|
||||
_, _, _, err = NewClientConn(c2, "", config)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestClientAuthPublicKey(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodPassword(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodFallback(t *testing.T) {
|
||||
var passwordCalled bool
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
PasswordCallback(
|
||||
func() (string, error) {
|
||||
passwordCalled = true
|
||||
return "WRONG", nil
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
|
||||
if passwordCalled {
|
||||
t.Errorf("password auth tried before public-key auth.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodWrongPassword(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
Password("wrong"),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodKeyboardInteractive(t *testing.T) {
|
||||
answers := keyboardInteractive(map[string]string{
|
||||
"question1": "answer1",
|
||||
"question2": "answer2",
|
||||
})
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodWrongKeyboardInteractive(t *testing.T) {
|
||||
answers := keyboardInteractive(map[string]string{
|
||||
"question1": "answer1",
|
||||
"question2": "WRONG",
|
||||
})
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive")
|
||||
}
|
||||
}
|
||||
|
||||
// the mock server will only authenticate ssh-rsa keys
|
||||
func TestAuthMethodInvalidPublicKey(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"]),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Fatalf("dsa private key should not have authenticated with rsa public key")
|
||||
}
|
||||
}
|
||||
|
||||
// the client should authenticate with the second key
|
||||
func TestAuthMethodRSAandDSA(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"], testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with rsa key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientHMAC(t *testing.T) {
|
||||
for _, mac := range supportedMACs {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
Config: Config{
|
||||
MACs: []string{mac},
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// issue 4285.
|
||||
func TestClientUnsupportedCipher(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(),
|
||||
},
|
||||
Config: Config{
|
||||
Ciphers: []string{"aes128-cbc"}, // not currently supported
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Errorf("expected no ciphers in common")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientUnsupportedKex(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(),
|
||||
},
|
||||
Config: Config{
|
||||
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
|
||||
t.Errorf("got %v, expected 'common algorithm'", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientLoginCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
|
||||
|
||||
t.Log("should succeed")
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("corrupted signature")
|
||||
cert.Signature.Blob[0]++
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with corrupted sig")
|
||||
}
|
||||
|
||||
t.Log("revoked")
|
||||
cert.Serial = 666
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("revoked cert login succeeded")
|
||||
}
|
||||
cert.Serial = 1
|
||||
|
||||
t.Log("sign with wrong key")
|
||||
cert.SignCert(rand.Reader, testSigners["dsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with non-authoritive key")
|
||||
}
|
||||
|
||||
t.Log("host cert")
|
||||
cert.CertType = HostCert
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with wrong type")
|
||||
}
|
||||
cert.CertType = UserCert
|
||||
|
||||
t.Log("principal specified")
|
||||
cert.ValidPrincipals = []string{"user"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("wrong principal specified")
|
||||
cert.ValidPrincipals = []string{"fred"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with wrong principal")
|
||||
}
|
||||
cert.ValidPrincipals = nil
|
||||
|
||||
t.Log("added critical option")
|
||||
cert.CriticalOptions = map[string]string{"root-access": "yes"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with unrecognized critical option")
|
||||
}
|
||||
|
||||
t.Log("allowed source address")
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login with source-address failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("disallowed source address")
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login with source-address succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func testPermissionsPassing(withPermissions bool, t *testing.T) {
|
||||
serverConfig := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if conn.User() == "nopermissions" {
|
||||
return nil, nil
|
||||
} else {
|
||||
return &Permissions{}, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
if withPermissions {
|
||||
clientConfig.User = "permissions"
|
||||
} else {
|
||||
clientConfig.User = "nopermissions"
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewClientConn(c2, "", clientConfig)
|
||||
serverConn, err := newServer(c1, serverConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p := serverConn.Permissions; (p != nil) != withPermissions {
|
||||
t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionsPassing(t *testing.T) {
|
||||
testPermissionsPassing(true, t)
|
||||
}
|
||||
|
||||
func TestNoPermissionsPassing(t *testing.T) {
|
||||
testPermissionsPassing(false, t)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
|
||||
clientConn, serverConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
receivedVersion := make(chan string, 1)
|
||||
go func() {
|
||||
version, err := readVersion(serverConn)
|
||||
if err != nil {
|
||||
receivedVersion <- ""
|
||||
} else {
|
||||
receivedVersion <- string(version)
|
||||
}
|
||||
serverConn.Close()
|
||||
}()
|
||||
NewClientConn(clientConn, "", config)
|
||||
actual := <-receivedVersion
|
||||
if actual != expected {
|
||||
t.Fatalf("got %s; want %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomClientVersion(t *testing.T) {
|
||||
version := "Test-Client-Version-0.0"
|
||||
testClientVersion(t, &ClientConfig{ClientVersion: version}, version)
|
||||
}
|
||||
|
||||
func TestDefaultClientVersion(t *testing.T) {
|
||||
testClientVersion(t, &ClientConfig{}, packageVersion)
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
)
|
||||
|
||||
// These are string constants in the SSH protocol.
|
||||
const (
|
||||
compressionNone = "none"
|
||||
serviceUserAuth = "ssh-userauth"
|
||||
serviceSSH = "ssh-connection"
|
||||
)
|
||||
|
||||
// supportedCiphers specifies the supported ciphers in preference order.
|
||||
var supportedCiphers = []string{
|
||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||
"aes128-gcm@openssh.com",
|
||||
"arcfour256", "arcfour128",
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
||||
// preference order.
|
||||
var supportedKexAlgos = []string{
|
||||
kexAlgoCurve25519SHA256,
|
||||
// P384 and P521 are not constant-time yet, but since we don't
|
||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01,
|
||||
|
||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
KeyAlgoRSA, KeyAlgoDSA,
|
||||
}
|
||||
|
||||
// supportedMACs specifies a default set of MAC algorithms in preference order.
|
||||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
||||
// because they have reached the end of their useful life.
|
||||
var supportedMACs = []string{
|
||||
"hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
||||
}
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
||||
// hashFuncs keeps the mapping of supported algorithms to their respective
|
||||
// hashes needed for signature verification.
|
||||
var hashFuncs = map[string]crypto.Hash{
|
||||
KeyAlgoRSA: crypto.SHA1,
|
||||
KeyAlgoDSA: crypto.SHA1,
|
||||
KeyAlgoECDSA256: crypto.SHA256,
|
||||
KeyAlgoECDSA384: crypto.SHA384,
|
||||
KeyAlgoECDSA521: crypto.SHA512,
|
||||
CertAlgoRSAv01: crypto.SHA1,
|
||||
CertAlgoDSAv01: crypto.SHA1,
|
||||
CertAlgoECDSA256v01: crypto.SHA256,
|
||||
CertAlgoECDSA384v01: crypto.SHA384,
|
||||
CertAlgoECDSA521v01: crypto.SHA512,
|
||||
}
|
||||
|
||||
// unexpectedMessageError results when the SSH message that we received didn't
|
||||
// match what we wanted.
|
||||
func unexpectedMessageError(expected, got uint8) error {
|
||||
return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected)
|
||||
}
|
||||
|
||||
// parseError results from a malformed SSH message.
|
||||
func parseError(tag uint8) error {
|
||||
return fmt.Errorf("ssh: parse error in message type %d", tag)
|
||||
}
|
||||
|
||||
func findCommon(what string, client []string, server []string) (common string, err error) {
|
||||
for _, c := range client {
|
||||
for _, s := range server {
|
||||
if c == s {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
|
||||
}
|
||||
|
||||
type directionAlgorithms struct {
|
||||
Cipher string
|
||||
MAC string
|
||||
Compression string
|
||||
}
|
||||
|
||||
type algorithms struct {
|
||||
kex string
|
||||
hostKey string
|
||||
w directionAlgorithms
|
||||
r directionAlgorithms
|
||||
}
|
||||
|
||||
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
|
||||
result := &algorithms{}
|
||||
|
||||
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// If rekeythreshold is too small, we can't make any progress sending
|
||||
// stuff.
|
||||
const minRekeyThreshold uint64 = 256
|
||||
|
||||
// Config contains configuration data common to both ServerConfig and
|
||||
// ClientConfig.
|
||||
type Config struct {
|
||||
// Rand provides the source of entropy for cryptographic
|
||||
// primitives. If Rand is nil, the cryptographic random reader
|
||||
// in package crypto/rand will be used.
|
||||
Rand io.Reader
|
||||
|
||||
// The maximum number of bytes sent or received after which a
|
||||
// new key is negotiated. It must be at least 256. If
|
||||
// unspecified, 1 gigabyte is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
// default set of algorithms is used.
|
||||
KeyExchanges []string
|
||||
|
||||
// The allowed cipher algorithms. If unspecified then a sensible
|
||||
// default is used.
|
||||
Ciphers []string
|
||||
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
||||
// is used.
|
||||
MACs []string
|
||||
}
|
||||
|
||||
// SetDefaults sets sensible values for unset fields in config. This is
|
||||
// exported for testing: Configs passed to SSH functions are copied and have
|
||||
// default values set automatically.
|
||||
func (c *Config) SetDefaults() {
|
||||
if c.Rand == nil {
|
||||
c.Rand = rand.Reader
|
||||
}
|
||||
if c.Ciphers == nil {
|
||||
c.Ciphers = supportedCiphers
|
||||
}
|
||||
var ciphers []string
|
||||
for _, c := range c.Ciphers {
|
||||
if cipherModes[c] != nil {
|
||||
// reject the cipher if we have no cipherModes definition
|
||||
ciphers = append(ciphers, c)
|
||||
}
|
||||
}
|
||||
c.Ciphers = ciphers
|
||||
|
||||
if c.KeyExchanges == nil {
|
||||
c.KeyExchanges = supportedKexAlgos
|
||||
}
|
||||
|
||||
if c.MACs == nil {
|
||||
c.MACs = supportedMACs
|
||||
}
|
||||
|
||||
if c.RekeyThreshold == 0 {
|
||||
// RFC 4253, section 9 suggests rekeying after 1G.
|
||||
c.RekeyThreshold = 1 << 30
|
||||
}
|
||||
if c.RekeyThreshold < minRekeyThreshold {
|
||||
c.RekeyThreshold = minRekeyThreshold
|
||||
}
|
||||
}
|
||||
|
||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
||||
// possession of a private key. See RFC 4252, section 7.
|
||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||
data := struct {
|
||||
Session []byte
|
||||
Type byte
|
||||
User string
|
||||
Service string
|
||||
Method string
|
||||
Sign bool
|
||||
Algo []byte
|
||||
PubKey []byte
|
||||
}{
|
||||
sessionId,
|
||||
msgUserAuthRequest,
|
||||
req.User,
|
||||
req.Service,
|
||||
req.Method,
|
||||
true,
|
||||
algo,
|
||||
pubKey,
|
||||
}
|
||||
return Marshal(data)
|
||||
}
|
||||
|
||||
func appendU16(buf []byte, n uint16) []byte {
|
||||
return append(buf, byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendU32(buf []byte, n uint32) []byte {
|
||||
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendU64(buf []byte, n uint64) []byte {
|
||||
return append(buf,
|
||||
byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
|
||||
byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendInt(buf []byte, n int) []byte {
|
||||
return appendU32(buf, uint32(n))
|
||||
}
|
||||
|
||||
func appendString(buf []byte, s string) []byte {
|
||||
buf = appendU32(buf, uint32(len(s)))
|
||||
buf = append(buf, s...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func appendBool(buf []byte, b bool) []byte {
|
||||
if b {
|
||||
return append(buf, 1)
|
||||
}
|
||||
return append(buf, 0)
|
||||
}
|
||||
|
||||
// newCond is a helper to hide the fact that there is no usable zero
|
||||
// value for sync.Cond.
|
||||
func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
|
||||
|
||||
// window represents the buffer available to clients
|
||||
// wishing to write to a channel.
|
||||
type window struct {
|
||||
*sync.Cond
|
||||
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
|
||||
writeWaiters int
|
||||
closed bool
|
||||
}
|
||||
|
||||
// add adds win to the amount of window available
|
||||
// for consumers.
|
||||
func (w *window) add(win uint32) bool {
|
||||
// a zero sized window adjust is a noop.
|
||||
if win == 0 {
|
||||
return true
|
||||
}
|
||||
w.L.Lock()
|
||||
if w.win+win < win {
|
||||
w.L.Unlock()
|
||||
return false
|
||||
}
|
||||
w.win += win
|
||||
// It is unusual that multiple goroutines would be attempting to reserve
|
||||
// window space, but not guaranteed. Use broadcast to notify all waiters
|
||||
// that additional window is available.
|
||||
w.Broadcast()
|
||||
w.L.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// close sets the window to closed, so all reservations fail
|
||||
// immediately.
|
||||
func (w *window) close() {
|
||||
w.L.Lock()
|
||||
w.closed = true
|
||||
w.Broadcast()
|
||||
w.L.Unlock()
|
||||
}
|
||||
|
||||
// reserve reserves win from the available window capacity.
|
||||
// If no capacity remains, reserve will block. reserve may
|
||||
// return less than requested.
|
||||
func (w *window) reserve(win uint32) (uint32, error) {
|
||||
var err error
|
||||
w.L.Lock()
|
||||
w.writeWaiters++
|
||||
w.Broadcast()
|
||||
for w.win == 0 && !w.closed {
|
||||
w.Wait()
|
||||
}
|
||||
w.writeWaiters--
|
||||
if w.win < win {
|
||||
win = w.win
|
||||
}
|
||||
w.win -= win
|
||||
if w.closed {
|
||||
err = io.EOF
|
||||
}
|
||||
w.L.Unlock()
|
||||
return win, err
|
||||
}
|
||||
|
||||
// waitWriterBlocked waits until some goroutine is blocked for further
|
||||
// writes. It is used in tests only.
|
||||
func (w *window) waitWriterBlocked() {
|
||||
w.Cond.L.Lock()
|
||||
for w.writeWaiters == 0 {
|
||||
w.Cond.Wait()
|
||||
}
|
||||
w.Cond.L.Unlock()
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// OpenChannelError is returned if the other side rejects an
|
||||
// OpenChannel request.
|
||||
type OpenChannelError struct {
|
||||
Reason RejectionReason
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *OpenChannelError) Error() string {
|
||||
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
|
||||
}
|
||||
|
||||
// ConnMetadata holds metadata for the connection.
|
||||
type ConnMetadata interface {
|
||||
// User returns the user ID for this connection.
|
||||
// It is empty if no authentication is used.
|
||||
User() string
|
||||
|
||||
// SessionID returns the sesson hash, also denoted by H.
|
||||
SessionID() []byte
|
||||
|
||||
// ClientVersion returns the client's version string as hashed
|
||||
// into the session ID.
|
||||
ClientVersion() []byte
|
||||
|
||||
// ServerVersion returns the server's version string as hashed
|
||||
// into the session ID.
|
||||
ServerVersion() []byte
|
||||
|
||||
// RemoteAddr returns the remote address for this connection.
|
||||
RemoteAddr() net.Addr
|
||||
|
||||
// LocalAddr returns the local address for this connection.
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
||||
// Conn represents an SSH connection for both server and client roles.
|
||||
// Conn is the basis for implementing an application layer, such
|
||||
// as ClientConn, which implements the traditional shell access for
|
||||
// clients.
|
||||
type Conn interface {
|
||||
ConnMetadata
|
||||
|
||||
// SendRequest sends a global request, and returns the
|
||||
// reply. If wantReply is true, it returns the response status
|
||||
// and payload. See also RFC4254, section 4.
|
||||
SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error)
|
||||
|
||||
// OpenChannel tries to open an channel. If the request is
|
||||
// rejected, it returns *OpenChannelError. On success it returns
|
||||
// the SSH Channel and a Go channel for incoming, out-of-band
|
||||
// requests. The Go channel must be serviced, or the
|
||||
// connection will hang.
|
||||
OpenChannel(name string, data []byte) (Channel, <-chan *Request, error)
|
||||
|
||||
// Close closes the underlying network connection
|
||||
Close() error
|
||||
|
||||
// Wait blocks until the connection has shut down, and returns the
|
||||
// error causing the shutdown.
|
||||
Wait() error
|
||||
|
||||
// TODO(hanwen): consider exposing:
|
||||
// RequestKeyChange
|
||||
// Disconnect
|
||||
}
|
||||
|
||||
// DiscardRequests consumes and rejects all requests from the
|
||||
// passed-in channel.
|
||||
func DiscardRequests(in <-chan *Request) {
|
||||
for req := range in {
|
||||
if req.WantReply {
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A connection represents an incoming connection.
|
||||
type connection struct {
|
||||
transport *handshakeTransport
|
||||
sshConn
|
||||
|
||||
// The connection protocol.
|
||||
*mux
|
||||
}
|
||||
|
||||
func (c *connection) Close() error {
|
||||
return c.sshConn.conn.Close()
|
||||
}
|
||||
|
||||
// sshconn provides net.Conn metadata, but disallows direct reads and
|
||||
// writes.
|
||||
type sshConn struct {
|
||||
conn net.Conn
|
||||
|
||||
user string
|
||||
sessionID []byte
|
||||
clientVersion []byte
|
||||
serverVersion []byte
|
||||
}
|
||||
|
||||
func dup(src []byte) []byte {
|
||||
dst := make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (c *sshConn) User() string {
|
||||
return c.user
|
||||
}
|
||||
|
||||
func (c *sshConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *sshConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) SessionID() []byte {
|
||||
return dup(c.sessionID)
|
||||
}
|
||||
|
||||
func (c *sshConn) ClientVersion() []byte {
|
||||
return dup(c.clientVersion)
|
||||
}
|
||||
|
||||
func (c *sshConn) ServerVersion() []byte {
|
||||
return dup(c.serverVersion)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package ssh implements an SSH client and server.
|
||||
|
||||
SSH is a transport security protocol, an authentication protocol and a
|
||||
family of application protocols. The most typical application level
|
||||
protocol is a remote shell and this is specifically implemented. However,
|
||||
the multiplexed nature of SSH is exposed to users that wish to support
|
||||
others.
|
||||
|
||||
References:
|
||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
||||
*/
|
||||
package ssh
|
||||
@@ -1,211 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
"github.com/gogits/gogs/modules/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func ExampleNewServerConn() {
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
panic("Failed to load private key")
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
panic("Failed to parse private key")
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
panic("failed to listen for connection")
|
||||
}
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
panic("failed to accept incoming connection")
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming
|
||||
// net.Conn.
|
||||
_, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
panic("failed to handshake")
|
||||
}
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
for newChannel := range chans {
|
||||
// Channels have a type, depending on the application level
|
||||
// protocol intended. In the case of a shell, the type is
|
||||
// "session" and ServerShell may be used to present a simple
|
||||
// terminal interface.
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
panic("could not accept channel.")
|
||||
}
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
// "pty-req" and "env". Here we handle only the
|
||||
// "shell" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "shell":
|
||||
ok = true
|
||||
if len(req.Payload) > 0 {
|
||||
// We don't accept any
|
||||
// commands, only the
|
||||
// default shell.
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
term := terminal.NewTerminal(channel, "> ")
|
||||
|
||||
go func() {
|
||||
defer channel.Close()
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fmt.Println(line)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleDial() {
|
||||
// An SSH client is represented with a ClientConn. Currently only
|
||||
// the "password" authentication method is supported.
|
||||
//
|
||||
// To authenticate with the remote server you must pass at least one
|
||||
// implementation of AuthMethod via the Auth field in ClientConfig.
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("yourpassword"),
|
||||
},
|
||||
}
|
||||
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
|
||||
if err != nil {
|
||||
panic("Failed to dial: " + err.Error())
|
||||
}
|
||||
|
||||
// Each ClientConn can support multiple interactive sessions,
|
||||
// represented by a Session.
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
panic("Failed to create session: " + err.Error())
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Once a Session is created, you can execute a single command on
|
||||
// the remote side using the Run method.
|
||||
var b bytes.Buffer
|
||||
session.Stdout = &b
|
||||
if err := session.Run("/usr/bin/whoami"); err != nil {
|
||||
panic("Failed to run: " + err.Error())
|
||||
}
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
|
||||
func ExampleClient_Listen() {
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("password"),
|
||||
},
|
||||
}
|
||||
// Dial your ssh server.
|
||||
conn, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect: %s", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Request the remote side to open port 8080 on all interfaces.
|
||||
l, err := conn.Listen("tcp", "0.0.0.0:8080")
|
||||
if err != nil {
|
||||
log.Fatalf("unable to register tcp forward: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// Serve HTTP with your SSH server acting as a reverse proxy.
|
||||
http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(resp, "Hello world!\n")
|
||||
}))
|
||||
}
|
||||
|
||||
func ExampleSession_RequestPty() {
|
||||
// Create client config
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("password"),
|
||||
},
|
||||
}
|
||||
// Connect to ssh server
|
||||
conn, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect: %s", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// Create a session
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
log.Fatalf("unable to create session: %s", err)
|
||||
}
|
||||
defer session.Close()
|
||||
// Set up terminal modes
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
// Request pseudo terminal
|
||||
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
|
||||
log.Fatalf("request for pseudo terminal failed: %s", err)
|
||||
}
|
||||
// Start remote shell
|
||||
if err := session.Shell(); err != nil {
|
||||
log.Fatalf("failed to start shell: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// debugHandshake, if set, prints messages sent and received. Key
|
||||
// exchange messages are printed as if DH were used, so the debug
|
||||
// messages are wrong when using ECDH.
|
||||
const debugHandshake = false
|
||||
|
||||
// keyingTransport is a packet based transport that supports key
|
||||
// changes. It need not be thread-safe. It should pass through
|
||||
// msgNewKeys in both directions.
|
||||
type keyingTransport interface {
|
||||
packetConn
|
||||
|
||||
// prepareKeyChange sets up a key change. The key change for a
|
||||
// direction will be effected if a msgNewKeys message is sent
|
||||
// or received.
|
||||
prepareKeyChange(*algorithms, *kexResult) error
|
||||
|
||||
// getSessionID returns the session ID. prepareKeyChange must
|
||||
// have been called once.
|
||||
getSessionID() []byte
|
||||
}
|
||||
|
||||
// rekeyingTransport is the interface of handshakeTransport that we
|
||||
// (internally) expose to ClientConn and ServerConn.
|
||||
type rekeyingTransport interface {
|
||||
packetConn
|
||||
|
||||
// requestKeyChange asks the remote side to change keys. All
|
||||
// writes are blocked until the key change succeeds, which is
|
||||
// signaled by reading a msgNewKeys.
|
||||
requestKeyChange() error
|
||||
|
||||
// getSessionID returns the session ID. This is only valid
|
||||
// after the first key change has completed.
|
||||
getSessionID() []byte
|
||||
}
|
||||
|
||||
// handshakeTransport implements rekeying on top of a keyingTransport
|
||||
// and offers a thread-safe writePacket() interface.
|
||||
type handshakeTransport struct {
|
||||
conn keyingTransport
|
||||
config *Config
|
||||
|
||||
serverVersion []byte
|
||||
clientVersion []byte
|
||||
|
||||
// hostKeys is non-empty if we are the server. In that case,
|
||||
// it contains all host keys that can be used to sign the
|
||||
// connection.
|
||||
hostKeys []Signer
|
||||
|
||||
// hostKeyAlgorithms is non-empty if we are the client. In that case,
|
||||
// we accept these key types from the server as host key.
|
||||
hostKeyAlgorithms []string
|
||||
|
||||
// On read error, incoming is closed, and readError is set.
|
||||
incoming chan []byte
|
||||
readError error
|
||||
|
||||
// data for host key checking
|
||||
hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
dialAddress string
|
||||
remoteAddr net.Addr
|
||||
|
||||
readSinceKex uint64
|
||||
|
||||
// Protects the writing side of the connection
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
sentInitPacket []byte
|
||||
sentInitMsg *kexInitMsg
|
||||
writtenSinceKex uint64
|
||||
writeError error
|
||||
}
|
||||
|
||||
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
|
||||
t := &handshakeTransport{
|
||||
conn: conn,
|
||||
serverVersion: serverVersion,
|
||||
clientVersion: clientVersion,
|
||||
incoming: make(chan []byte, 16),
|
||||
config: config,
|
||||
}
|
||||
t.cond = sync.NewCond(&t.mu)
|
||||
return t
|
||||
}
|
||||
|
||||
func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport {
|
||||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
||||
t.dialAddress = dialAddr
|
||||
t.remoteAddr = addr
|
||||
t.hostKeyCallback = config.HostKeyCallback
|
||||
if config.HostKeyAlgorithms != nil {
|
||||
t.hostKeyAlgorithms = config.HostKeyAlgorithms
|
||||
} else {
|
||||
t.hostKeyAlgorithms = supportedHostKeyAlgos
|
||||
}
|
||||
go t.readLoop()
|
||||
return t
|
||||
}
|
||||
|
||||
func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport {
|
||||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
||||
t.hostKeys = config.hostKeys
|
||||
go t.readLoop()
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) getSessionID() []byte {
|
||||
return t.conn.getSessionID()
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) id() string {
|
||||
if len(t.hostKeys) > 0 {
|
||||
return "server"
|
||||
}
|
||||
return "client"
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readPacket() ([]byte, error) {
|
||||
p, ok := <-t.incoming
|
||||
if !ok {
|
||||
return nil, t.readError
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readLoop() {
|
||||
for {
|
||||
p, err := t.readOnePacket()
|
||||
if err != nil {
|
||||
t.readError = err
|
||||
close(t.incoming)
|
||||
break
|
||||
}
|
||||
if p[0] == msgIgnore || p[0] == msgDebug {
|
||||
continue
|
||||
}
|
||||
t.incoming <- p
|
||||
}
|
||||
|
||||
// If we can't read, declare the writing part dead too.
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.writeError == nil {
|
||||
t.writeError = t.readError
|
||||
}
|
||||
t.cond.Broadcast()
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readOnePacket() ([]byte, error) {
|
||||
if t.readSinceKex > t.config.RekeyThreshold {
|
||||
if err := t.requestKeyChange(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p, err := t.conn.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.readSinceKex += uint64(len(p))
|
||||
if debugHandshake {
|
||||
msg, err := decode(p)
|
||||
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
|
||||
}
|
||||
if p[0] != msgKexInit {
|
||||
return p, nil
|
||||
}
|
||||
err = t.enterKeyExchange(p)
|
||||
|
||||
t.mu.Lock()
|
||||
if err != nil {
|
||||
// drop connection
|
||||
t.conn.Close()
|
||||
t.writeError = err
|
||||
}
|
||||
|
||||
if debugHandshake {
|
||||
log.Printf("%s exited key exchange, err %v", t.id(), err)
|
||||
}
|
||||
|
||||
// Unblock writers.
|
||||
t.sentInitMsg = nil
|
||||
t.sentInitPacket = nil
|
||||
t.cond.Broadcast()
|
||||
t.writtenSinceKex = 0
|
||||
t.mu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.readSinceKex = 0
|
||||
return []byte{msgNewKeys}, nil
|
||||
}
|
||||
|
||||
// sendKexInit sends a key change message, and returns the message
|
||||
// that was sent. After initiating the key change, all writes will be
|
||||
// blocked until the change is done, and a failed key change will
|
||||
// close the underlying transport. This function is safe for
|
||||
// concurrent use by multiple goroutines.
|
||||
func (t *handshakeTransport) sendKexInit() (*kexInitMsg, []byte, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.sendKexInitLocked()
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) requestKeyChange() error {
|
||||
_, _, err := t.sendKexInit()
|
||||
return err
|
||||
}
|
||||
|
||||
// sendKexInitLocked sends a key change message. t.mu must be locked
|
||||
// while this happens.
|
||||
func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
|
||||
// kexInits may be sent either in response to the other side,
|
||||
// or because our side wants to initiate a key change, so we
|
||||
// may have already sent a kexInit. In that case, don't send a
|
||||
// second kexInit.
|
||||
if t.sentInitMsg != nil {
|
||||
return t.sentInitMsg, t.sentInitPacket, nil
|
||||
}
|
||||
msg := &kexInitMsg{
|
||||
KexAlgos: t.config.KeyExchanges,
|
||||
CiphersClientServer: t.config.Ciphers,
|
||||
CiphersServerClient: t.config.Ciphers,
|
||||
MACsClientServer: t.config.MACs,
|
||||
MACsServerClient: t.config.MACs,
|
||||
CompressionClientServer: supportedCompressions,
|
||||
CompressionServerClient: supportedCompressions,
|
||||
}
|
||||
io.ReadFull(rand.Reader, msg.Cookie[:])
|
||||
|
||||
if len(t.hostKeys) > 0 {
|
||||
for _, k := range t.hostKeys {
|
||||
msg.ServerHostKeyAlgos = append(
|
||||
msg.ServerHostKeyAlgos, k.PublicKey().Type())
|
||||
}
|
||||
} else {
|
||||
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
|
||||
}
|
||||
packet := Marshal(msg)
|
||||
|
||||
// writePacket destroys the contents, so save a copy.
|
||||
packetCopy := make([]byte, len(packet))
|
||||
copy(packetCopy, packet)
|
||||
|
||||
if err := t.conn.writePacket(packetCopy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t.sentInitMsg = msg
|
||||
t.sentInitPacket = packet
|
||||
return msg, packet, nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) writePacket(p []byte) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.writtenSinceKex > t.config.RekeyThreshold {
|
||||
t.sendKexInitLocked()
|
||||
}
|
||||
for t.sentInitMsg != nil && t.writeError == nil {
|
||||
t.cond.Wait()
|
||||
}
|
||||
if t.writeError != nil {
|
||||
return t.writeError
|
||||
}
|
||||
t.writtenSinceKex += uint64(len(p))
|
||||
|
||||
switch p[0] {
|
||||
case msgKexInit:
|
||||
return errors.New("ssh: only handshakeTransport can send kexInit")
|
||||
case msgNewKeys:
|
||||
return errors.New("ssh: only handshakeTransport can send newKeys")
|
||||
default:
|
||||
return t.conn.writePacket(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) Close() error {
|
||||
return t.conn.Close()
|
||||
}
|
||||
|
||||
// enterKeyExchange runs the key exchange.
|
||||
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||
if debugHandshake {
|
||||
log.Printf("%s entered key exchange", t.id())
|
||||
}
|
||||
myInit, myInitPacket, err := t.sendKexInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherInit := &kexInitMsg{}
|
||||
if err := Unmarshal(otherInitPacket, otherInit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
magics := handshakeMagics{
|
||||
clientVersion: t.clientVersion,
|
||||
serverVersion: t.serverVersion,
|
||||
clientKexInit: otherInitPacket,
|
||||
serverKexInit: myInitPacket,
|
||||
}
|
||||
|
||||
clientInit := otherInit
|
||||
serverInit := myInit
|
||||
if len(t.hostKeys) == 0 {
|
||||
clientInit = myInit
|
||||
serverInit = otherInit
|
||||
|
||||
magics.clientKexInit = myInitPacket
|
||||
magics.serverKexInit = otherInitPacket
|
||||
}
|
||||
|
||||
algs, err := findAgreedAlgorithms(clientInit, serverInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We don't send FirstKexFollows, but we handle receiving it.
|
||||
if otherInit.FirstKexFollows && algs.kex != otherInit.KexAlgos[0] {
|
||||
// other side sent a kex message for the wrong algorithm,
|
||||
// which we have to ignore.
|
||||
if _, err := t.conn.readPacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
kex, ok := kexAlgoMap[algs.kex]
|
||||
if !ok {
|
||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
|
||||
}
|
||||
|
||||
var result *kexResult
|
||||
if len(t.hostKeys) > 0 {
|
||||
result, err = t.server(kex, algs, &magics)
|
||||
} else {
|
||||
result, err = t.client(kex, algs, &magics)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.conn.prepareKeyChange(algs, result)
|
||||
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
||||
return err
|
||||
}
|
||||
if packet, err := t.conn.readPacket(); err != nil {
|
||||
return err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
|
||||
var hostKey Signer
|
||||
for _, k := range t.hostKeys {
|
||||
if algs.hostKey == k.PublicKey().Type() {
|
||||
hostKey = k
|
||||
}
|
||||
}
|
||||
|
||||
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
|
||||
result, err := kex.Client(t.conn, t.config.Rand, magics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostKey, err := ParsePublicKey(result.HostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := verifyHostKeySignature(hostKey, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.hostKeyCallback != nil {
|
||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testChecker struct {
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
||||
if dialAddr == "bad" {
|
||||
return fmt.Errorf("dialAddr is bad")
|
||||
}
|
||||
|
||||
if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil {
|
||||
return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr)
|
||||
}
|
||||
|
||||
t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||
// a write.)
|
||||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c2, err := listener.Accept()
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c1, c2, nil
|
||||
}
|
||||
|
||||
func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTransport, server *handshakeTransport, err error) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
trC := newTransport(a, rand.Reader, true)
|
||||
trS := newTransport(b, rand.Reader, false)
|
||||
clientConf.SetDefaults()
|
||||
|
||||
v := []byte("version")
|
||||
client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr())
|
||||
|
||||
serverConf := &ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.SetDefaults()
|
||||
server = newServerTransport(trS, v, v, serverConf)
|
||||
|
||||
return client, server, nil
|
||||
}
|
||||
|
||||
func TestHandshakeBasic(t *testing.T) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("see golang.org/issue/7237")
|
||||
}
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
go func() {
|
||||
// Client writes a bunch of stuff, and does a key
|
||||
// change in the middle. This should not confuse the
|
||||
// handshake in progress
|
||||
for i := 0; i < 10; i++ {
|
||||
p := []byte{msgRequestSuccess, byte(i)}
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
}
|
||||
if i == 5 {
|
||||
// halfway through, we request a key change.
|
||||
_, _, err := trC.sendKexInit()
|
||||
if err != nil {
|
||||
t.Fatalf("sendKexInit: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
trC.Close()
|
||||
}()
|
||||
|
||||
// Server checks that client messages come in cleanly
|
||||
i := 0
|
||||
for {
|
||||
p, err := trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if p[0] == msgNewKeys {
|
||||
continue
|
||||
}
|
||||
want := []byte{msgRequestSuccess, byte(i)}
|
||||
if bytes.Compare(p, want) != 0 {
|
||||
t.Errorf("message %d: got %q, want %q", i, p, want)
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i != 10 {
|
||||
t.Errorf("received %d messages, want 10.", i)
|
||||
}
|
||||
|
||||
// If all went well, we registered exactly 1 key change.
|
||||
if len(checker.calls) != 1 {
|
||||
t.Fatalf("got %d host key checks, want 1", len(checker.calls))
|
||||
}
|
||||
|
||||
pub := testSigners["ecdsa"].PublicKey()
|
||||
want := fmt.Sprintf("%s %v %s %x", "addr", trC.remoteAddr, pub.Type(), pub.Marshal())
|
||||
if want != checker.calls[0] {
|
||||
t.Errorf("got %q want %q for host key check", checker.calls[0], want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeError(t *testing.T) {
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "bad")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
// send a packet
|
||||
packet := []byte{msgRequestSuccess, 42}
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
// Now request a key change.
|
||||
_, _, err = trC.sendKexInit()
|
||||
if err != nil {
|
||||
t.Errorf("sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
// the key change will fail, and afterwards we can't write.
|
||||
if err := trC.writePacket([]byte{msgRequestSuccess, 43}); err == nil {
|
||||
t.Errorf("writePacket after botched rekey succeeded.")
|
||||
}
|
||||
|
||||
readback, err := trS.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("server closed too soon: %v", err)
|
||||
}
|
||||
if bytes.Compare(readback, packet) != 0 {
|
||||
t.Errorf("got %q want %q", readback, packet)
|
||||
}
|
||||
readback, err = trS.readPacket()
|
||||
if err == nil {
|
||||
t.Errorf("got a message %q after failed key change", readback)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeTwice(t *testing.T) {
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
// send a packet
|
||||
packet := make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
// Now request a key change.
|
||||
_, _, err = trC.sendKexInit()
|
||||
if err != nil {
|
||||
t.Errorf("sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
// Send another packet. Use a fresh one, since writePacket destroys.
|
||||
packet = make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
// 2nd key change.
|
||||
_, _, err = trC.sendKexInit()
|
||||
if err != nil {
|
||||
t.Errorf("sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
packet = make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
packet = make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
for i := 0; i < 5; i++ {
|
||||
msg, err := trS.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("server closed too soon: %v", err)
|
||||
}
|
||||
if msg[0] == msgNewKeys {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Compare(msg, packet) != 0 {
|
||||
t.Errorf("packet %d: got %q want %q", i, msg, packet)
|
||||
}
|
||||
}
|
||||
if len(checker.calls) != 2 {
|
||||
t.Errorf("got %d key changes, want 2", len(checker.calls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeAutoRekeyWrite(t *testing.T) {
|
||||
checker := &testChecker{}
|
||||
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
||||
clientConf.RekeyThreshold = 500
|
||||
trC, trS, err := handshakePair(clientConf, "addr")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
packet := make([]byte, 251)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
j := 0
|
||||
for ; j < 5; j++ {
|
||||
_, err := trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if j != 5 {
|
||||
t.Errorf("got %d, want 5 messages", j)
|
||||
}
|
||||
|
||||
if len(checker.calls) != 2 {
|
||||
t.Errorf("got %d key changes, wanted 2", len(checker.calls))
|
||||
}
|
||||
}
|
||||
|
||||
type syncChecker struct {
|
||||
called chan int
|
||||
}
|
||||
|
||||
func (t *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
||||
t.called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHandshakeAutoRekeyRead(t *testing.T) {
|
||||
sync := &syncChecker{make(chan int, 2)}
|
||||
clientConf := &ClientConfig{
|
||||
HostKeyCallback: sync.Check,
|
||||
}
|
||||
clientConf.RekeyThreshold = 500
|
||||
|
||||
trC, trS, err := handshakePair(clientConf, "addr")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
packet := make([]byte, 501)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trS.writePacket(packet); err != nil {
|
||||
t.Fatalf("writePacket: %v", err)
|
||||
}
|
||||
// While we read out the packet, a key change will be
|
||||
// initiated.
|
||||
if _, err := trC.readPacket(); err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
<-sync.called
|
||||
}
|
||||
|
||||
// errorKeyingTransport generates errors after a given number of
|
||||
// read/write operations.
|
||||
type errorKeyingTransport struct {
|
||||
packetConn
|
||||
readLeft, writeLeft int
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
|
||||
return nil
|
||||
}
|
||||
func (n *errorKeyingTransport) getSessionID() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) writePacket(packet []byte) error {
|
||||
if n.writeLeft == 0 {
|
||||
n.Close()
|
||||
return errors.New("barf")
|
||||
}
|
||||
|
||||
n.writeLeft--
|
||||
return n.packetConn.writePacket(packet)
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) readPacket() ([]byte, error) {
|
||||
if n.readLeft == 0 {
|
||||
n.Close()
|
||||
return nil, errors.New("barf")
|
||||
}
|
||||
|
||||
n.readLeft--
|
||||
return n.packetConn.readPacket()
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingRead(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, i, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingWrite(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, -1, i)
|
||||
}
|
||||
}
|
||||
|
||||
// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
|
||||
// handshakeTransport deadlocks, the go runtime will detect it and
|
||||
// panic.
|
||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int) {
|
||||
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
||||
|
||||
a, b := memPipe()
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
key := testSigners["ecdsa"]
|
||||
serverConf := Config{RekeyThreshold: minRekeyThreshold}
|
||||
serverConf.SetDefaults()
|
||||
serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
|
||||
serverConn.hostKeys = []Signer{key}
|
||||
go serverConn.readLoop()
|
||||
|
||||
clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
|
||||
clientConf.SetDefaults()
|
||||
clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
|
||||
clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
|
||||
go clientConn.readLoop()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(4)
|
||||
|
||||
for _, hs := range []packetConn{serverConn, clientConn} {
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
err := c.writePacket(msg)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
_, err := c.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/subtle"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const (
|
||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
||||
kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
|
||||
)
|
||||
|
||||
// kexResult captures the outcome of a key exchange.
|
||||
type kexResult struct {
|
||||
// Session hash. See also RFC 4253, section 8.
|
||||
H []byte
|
||||
|
||||
// Shared secret. See also RFC 4253, section 8.
|
||||
K []byte
|
||||
|
||||
// Host key as hashed into H.
|
||||
HostKey []byte
|
||||
|
||||
// Signature of H.
|
||||
Signature []byte
|
||||
|
||||
// A cryptographic hash function that matches the security
|
||||
// level of the key exchange algorithm. It is used for
|
||||
// calculating H, and for deriving keys from H and K.
|
||||
Hash crypto.Hash
|
||||
|
||||
// The session ID, which is the first H computed. This is used
|
||||
// to signal data inside transport.
|
||||
SessionID []byte
|
||||
}
|
||||
|
||||
// handshakeMagics contains data that is always included in the
|
||||
// session hash.
|
||||
type handshakeMagics struct {
|
||||
clientVersion, serverVersion []byte
|
||||
clientKexInit, serverKexInit []byte
|
||||
}
|
||||
|
||||
func (m *handshakeMagics) write(w io.Writer) {
|
||||
writeString(w, m.clientVersion)
|
||||
writeString(w, m.serverVersion)
|
||||
writeString(w, m.clientKexInit)
|
||||
writeString(w, m.serverKexInit)
|
||||
}
|
||||
|
||||
// kexAlgorithm abstracts different key exchange algorithms.
|
||||
type kexAlgorithm interface {
|
||||
// Server runs server-side key agreement, signing the result
|
||||
// with a hostkey.
|
||||
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error)
|
||||
|
||||
// Client runs the client-side key agreement. Caller is
|
||||
// responsible for verifying the host key signature.
|
||||
Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error)
|
||||
}
|
||||
|
||||
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
||||
type dhGroup struct {
|
||||
g, p *big.Int
|
||||
}
|
||||
|
||||
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
|
||||
if theirPublic.Sign() <= 0 || theirPublic.Cmp(group.p) >= 0 {
|
||||
return nil, errors.New("ssh: DH parameter out of bounds")
|
||||
}
|
||||
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
|
||||
}
|
||||
|
||||
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
hashFunc := crypto.SHA1
|
||||
|
||||
x, err := rand.Int(randSource, group.p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
X := new(big.Int).Exp(group.g, x, group.p)
|
||||
kexDHInit := kexDHInitMsg{
|
||||
X: X,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&kexDHInit)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kexDHReply kexDHReplyMsg
|
||||
if err = Unmarshal(packet, &kexDHReply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kInt, err := group.diffieHellman(kexDHReply.Y, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := hashFunc.New()
|
||||
magics.write(h)
|
||||
writeString(h, kexDHReply.HostKey)
|
||||
writeInt(h, X)
|
||||
writeInt(h, kexDHReply.Y)
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: kexDHReply.HostKey,
|
||||
Signature: kexDHReply.Signature,
|
||||
Hash: crypto.SHA1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||
hashFunc := crypto.SHA1
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var kexDHInit kexDHInitMsg
|
||||
if err = Unmarshal(packet, &kexDHInit); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
y, err := rand.Int(randSource, group.p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
||||
kInt, err := group.diffieHellman(kexDHInit.X, y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
h := hashFunc.New()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeInt(h, kexDHInit.X)
|
||||
writeInt(h, Y)
|
||||
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
// H is already a hash, but the hostkey signing will apply its
|
||||
// own key-specific hash algorithm.
|
||||
sig, err := signAndMarshal(priv, randSource, H)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kexDHReply := kexDHReplyMsg{
|
||||
HostKey: hostKeyBytes,
|
||||
Y: Y,
|
||||
Signature: sig,
|
||||
}
|
||||
packet = Marshal(&kexDHReply)
|
||||
|
||||
err = c.writePacket(packet)
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
Hash: crypto.SHA1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ecdh performs Elliptic Curve Diffie-Hellman key exchange as
|
||||
// described in RFC 5656, section 4.
|
||||
type ecdh struct {
|
||||
curve elliptic.Curve
|
||||
}
|
||||
|
||||
func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kexInit := kexECDHInitMsg{
|
||||
ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y),
|
||||
}
|
||||
|
||||
serialized := Marshal(&kexInit)
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply kexECDHReplyMsg
|
||||
if err = Unmarshal(packet, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// generate shared secret
|
||||
secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes())
|
||||
|
||||
h := ecHash(kex.curve).New()
|
||||
magics.write(h)
|
||||
writeString(h, reply.HostKey)
|
||||
writeString(h, kexInit.ClientPubKey)
|
||||
writeString(h, reply.EphemeralPubKey)
|
||||
K := make([]byte, intLength(secret))
|
||||
marshalInt(K, secret)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: reply.Signature,
|
||||
Hash: ecHash(kex.curve),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// unmarshalECKey parses and checks an EC key.
|
||||
func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) {
|
||||
x, y = elliptic.Unmarshal(curve, pubkey)
|
||||
if x == nil {
|
||||
return nil, nil, errors.New("ssh: elliptic.Unmarshal failure")
|
||||
}
|
||||
if !validateECPublicKey(curve, x, y) {
|
||||
return nil, nil, errors.New("ssh: public key not on curve")
|
||||
}
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
// validateECPublicKey checks that the point is a valid public key for
|
||||
// the given curve. See [SEC1], 3.2.2
|
||||
func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool {
|
||||
if x.Sign() == 0 && y.Sign() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if x.Cmp(curve.Params().P) >= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if y.Cmp(curve.Params().P) >= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if !curve.IsOnCurve(x, y) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We don't check if N * PubKey == 0, since
|
||||
//
|
||||
// - the NIST curves have cofactor = 1, so this is implicit.
|
||||
// (We don't foresee an implementation that supports non NIST
|
||||
// curves)
|
||||
//
|
||||
// - for ephemeral keys, we don't need to worry about small
|
||||
// subgroup attacks.
|
||||
return true
|
||||
}
|
||||
|
||||
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kexECDHInit kexECDHInitMsg
|
||||
if err = Unmarshal(packet, &kexECDHInit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We could cache this key across multiple users/multiple
|
||||
// connection attempts, but the benefit is small. OpenSSH
|
||||
// generates a new key for each incoming connection.
|
||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y)
|
||||
|
||||
// generate shared secret
|
||||
secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes())
|
||||
|
||||
h := ecHash(kex.curve).New()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeString(h, kexECDHInit.ClientPubKey)
|
||||
writeString(h, serializedEphKey)
|
||||
|
||||
K := make([]byte, intLength(secret))
|
||||
marshalInt(K, secret)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
// H is already a hash, but the hostkey signing will apply its
|
||||
// own key-specific hash algorithm.
|
||||
sig, err := signAndMarshal(priv, rand, H)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := kexECDHReplyMsg{
|
||||
EphemeralPubKey: serializedEphKey,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
}
|
||||
|
||||
serialized := Marshal(&reply)
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: sig,
|
||||
Hash: ecHash(kex.curve),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var kexAlgoMap = map[string]kexAlgorithm{}
|
||||
|
||||
func init() {
|
||||
// This is the group called diffie-hellman-group1-sha1 in RFC
|
||||
// 4253 and Oakley Group 2 in RFC 2409.
|
||||
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
|
||||
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
}
|
||||
|
||||
// This is the group called diffie-hellman-group14-sha1 in RFC
|
||||
// 4253 and Oakley Group 14 in RFC 3526.
|
||||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
||||
|
||||
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
}
|
||||
|
||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
||||
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
|
||||
}
|
||||
|
||||
// curve25519sha256 implements the curve25519-sha256@libssh.org key
|
||||
// agreement protocol, as described in
|
||||
// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
|
||||
type curve25519sha256 struct{}
|
||||
|
||||
type curve25519KeyPair struct {
|
||||
priv [32]byte
|
||||
pub [32]byte
|
||||
}
|
||||
|
||||
func (kp *curve25519KeyPair) generate(rand io.Reader) error {
|
||||
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
|
||||
return nil
|
||||
}
|
||||
|
||||
// curve25519Zeros is just an array of 32 zero bytes so that we have something
|
||||
// convenient to compare against in order to reject curve25519 points with the
|
||||
// wrong order.
|
||||
var curve25519Zeros [32]byte
|
||||
|
||||
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
var kp curve25519KeyPair
|
||||
if err := kp.generate(rand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply kexECDHReplyMsg
|
||||
if err = Unmarshal(packet, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(reply.EphemeralPubKey) != 32 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||
}
|
||||
|
||||
var servPub, secret [32]byte
|
||||
copy(servPub[:], reply.EphemeralPubKey)
|
||||
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
|
||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||
}
|
||||
|
||||
h := crypto.SHA256.New()
|
||||
magics.write(h)
|
||||
writeString(h, reply.HostKey)
|
||||
writeString(h, kp.pub[:])
|
||||
writeString(h, reply.EphemeralPubKey)
|
||||
|
||||
kInt := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: reply.Signature,
|
||||
Hash: crypto.SHA256,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var kexInit kexECDHInitMsg
|
||||
if err = Unmarshal(packet, &kexInit); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(kexInit.ClientPubKey) != 32 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||
}
|
||||
|
||||
var kp curve25519KeyPair
|
||||
if err := kp.generate(rand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var clientPub, secret [32]byte
|
||||
copy(clientPub[:], kexInit.ClientPubKey)
|
||||
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
|
||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
h := crypto.SHA256.New()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeString(h, kexInit.ClientPubKey)
|
||||
writeString(h, kp.pub[:])
|
||||
|
||||
kInt := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
sig, err := signAndMarshal(priv, rand, H)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := kexECDHReplyMsg{
|
||||
EphemeralPubKey: kp.pub[:],
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&reply)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
Hash: crypto.SHA256,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Key exchange tests.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKexes(t *testing.T) {
|
||||
type kexResultErr struct {
|
||||
result *kexResult
|
||||
err error
|
||||
}
|
||||
|
||||
for name, kex := range kexAlgoMap {
|
||||
a, b := memPipe()
|
||||
|
||||
s := make(chan kexResultErr, 1)
|
||||
c := make(chan kexResultErr, 1)
|
||||
var magics handshakeMagics
|
||||
go func() {
|
||||
r, e := kex.Client(a, rand.Reader, &magics)
|
||||
a.Close()
|
||||
c <- kexResultErr{r, e}
|
||||
}()
|
||||
go func() {
|
||||
r, e := kex.Server(b, rand.Reader, &magics, testSigners["ecdsa"])
|
||||
b.Close()
|
||||
s <- kexResultErr{r, e}
|
||||
}()
|
||||
|
||||
clientRes := <-c
|
||||
serverRes := <-s
|
||||
if clientRes.err != nil {
|
||||
t.Errorf("client: %v", clientRes.err)
|
||||
}
|
||||
if serverRes.err != nil {
|
||||
t.Errorf("server: %v", serverRes.err)
|
||||
}
|
||||
if !reflect.DeepEqual(clientRes.result, serverRes.result) {
|
||||
t.Errorf("kex %q: mismatch %#v, %#v", name, clientRes.result, serverRes.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,628 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// These constants represent the algorithm names for key types supported by this
|
||||
// package.
|
||||
const (
|
||||
KeyAlgoRSA = "ssh-rsa"
|
||||
KeyAlgoDSA = "ssh-dss"
|
||||
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
||||
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
||||
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
||||
)
|
||||
|
||||
// parsePubKey parses a public key of the given algorithm.
|
||||
// Use ParsePublicKey for keys with prepended algorithm.
|
||||
func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) {
|
||||
switch algo {
|
||||
case KeyAlgoRSA:
|
||||
return parseRSA(in)
|
||||
case KeyAlgoDSA:
|
||||
return parseDSA(in)
|
||||
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
||||
return parseECDSA(in)
|
||||
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
||||
cert, err := parseCert(in, certToPrivAlgo(algo))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cert, nil, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", err)
|
||||
}
|
||||
|
||||
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
||||
// (see sshd(8) manual page) once the options and key type fields have been
|
||||
// removed.
|
||||
func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) {
|
||||
in = bytes.TrimSpace(in)
|
||||
|
||||
i := bytes.IndexAny(in, " \t")
|
||||
if i == -1 {
|
||||
i = len(in)
|
||||
}
|
||||
base64Key := in[:i]
|
||||
|
||||
key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key)))
|
||||
n, err := base64.StdEncoding.Decode(key, base64Key)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
key = key[:n]
|
||||
out, err = ParsePublicKey(key)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
comment = string(bytes.TrimSpace(in[i:]))
|
||||
return out, comment, nil
|
||||
}
|
||||
|
||||
// ParseAuthorizedKeys parses a public key from an authorized_keys
|
||||
// file used in OpenSSH according to the sshd(8) manual page.
|
||||
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
||||
for len(in) > 0 {
|
||||
end := bytes.IndexByte(in, '\n')
|
||||
if end != -1 {
|
||||
rest = in[end+1:]
|
||||
in = in[:end]
|
||||
} else {
|
||||
rest = nil
|
||||
}
|
||||
|
||||
end = bytes.IndexByte(in, '\r')
|
||||
if end != -1 {
|
||||
in = in[:end]
|
||||
}
|
||||
|
||||
in = bytes.TrimSpace(in)
|
||||
if len(in) == 0 || in[0] == '#' {
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
i := bytes.IndexAny(in, " \t")
|
||||
if i == -1 {
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
||||
return out, comment, options, rest, nil
|
||||
}
|
||||
|
||||
// No key type recognised. Maybe there's an options field at
|
||||
// the beginning.
|
||||
var b byte
|
||||
inQuote := false
|
||||
var candidateOptions []string
|
||||
optionStart := 0
|
||||
for i, b = range in {
|
||||
isEnd := !inQuote && (b == ' ' || b == '\t')
|
||||
if (b == ',' && !inQuote) || isEnd {
|
||||
if i-optionStart > 0 {
|
||||
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
|
||||
}
|
||||
optionStart = i + 1
|
||||
}
|
||||
if isEnd {
|
||||
break
|
||||
}
|
||||
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
|
||||
inQuote = !inQuote
|
||||
}
|
||||
}
|
||||
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i == len(in) {
|
||||
// Invalid line: unmatched quote
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
in = in[i:]
|
||||
i = bytes.IndexAny(in, " \t")
|
||||
if i == -1 {
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
||||
options = candidateOptions
|
||||
return out, comment, options, rest, nil
|
||||
}
|
||||
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, "", nil, nil, errors.New("ssh: no key found")
|
||||
}
|
||||
|
||||
// ParsePublicKey parses an SSH public key formatted for use in
|
||||
// the SSH wire protocol according to RFC 4253, section 6.6.
|
||||
func ParsePublicKey(in []byte) (out PublicKey, err error) {
|
||||
algo, in, ok := parseString(in)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
var rest []byte
|
||||
out, rest, err = parsePubKey(in, string(algo))
|
||||
if len(rest) > 0 {
|
||||
return nil, errors.New("ssh: trailing junk in public key")
|
||||
}
|
||||
|
||||
return out, err
|
||||
}
|
||||
|
||||
// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH
|
||||
// authorized_keys file. The return value ends with newline.
|
||||
func MarshalAuthorizedKey(key PublicKey) []byte {
|
||||
b := &bytes.Buffer{}
|
||||
b.WriteString(key.Type())
|
||||
b.WriteByte(' ')
|
||||
e := base64.NewEncoder(base64.StdEncoding, b)
|
||||
e.Write(key.Marshal())
|
||||
e.Close()
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// PublicKey is an abstraction of different types of public keys.
|
||||
type PublicKey interface {
|
||||
// Type returns the key's type, e.g. "ssh-rsa".
|
||||
Type() string
|
||||
|
||||
// Marshal returns the serialized key data in SSH wire format,
|
||||
// with the name prefix.
|
||||
Marshal() []byte
|
||||
|
||||
// Verify that sig is a signature on the given data using this
|
||||
// key. This function will hash the data appropriately first.
|
||||
Verify(data []byte, sig *Signature) error
|
||||
}
|
||||
|
||||
// A Signer can create signatures that verify against a public key.
|
||||
type Signer interface {
|
||||
// PublicKey returns an associated PublicKey instance.
|
||||
PublicKey() PublicKey
|
||||
|
||||
// Sign returns raw signature for the given data. This method
|
||||
// will apply the hash specified for the keytype to the data.
|
||||
Sign(rand io.Reader, data []byte) (*Signature, error)
|
||||
}
|
||||
|
||||
type rsaPublicKey rsa.PublicKey
|
||||
|
||||
func (r *rsaPublicKey) Type() string {
|
||||
return "ssh-rsa"
|
||||
}
|
||||
|
||||
// parseRSA parses an RSA key according to RFC 4253, section 6.6.
|
||||
func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
var w struct {
|
||||
E *big.Int
|
||||
N *big.Int
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
if err := Unmarshal(in, &w); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if w.E.BitLen() > 24 {
|
||||
return nil, nil, errors.New("ssh: exponent too large")
|
||||
}
|
||||
e := w.E.Int64()
|
||||
if e < 3 || e&1 == 0 {
|
||||
return nil, nil, errors.New("ssh: incorrect exponent")
|
||||
}
|
||||
|
||||
var key rsa.PublicKey
|
||||
key.E = int(e)
|
||||
key.N = w.N
|
||||
return (*rsaPublicKey)(&key), w.Rest, nil
|
||||
}
|
||||
|
||||
func (r *rsaPublicKey) Marshal() []byte {
|
||||
e := new(big.Int).SetInt64(int64(r.E))
|
||||
wirekey := struct {
|
||||
Name string
|
||||
E *big.Int
|
||||
N *big.Int
|
||||
}{
|
||||
KeyAlgoRSA,
|
||||
e,
|
||||
r.N,
|
||||
}
|
||||
return Marshal(&wirekey)
|
||||
}
|
||||
|
||||
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != r.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
||||
}
|
||||
h := crypto.SHA1.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
|
||||
}
|
||||
|
||||
type rsaPrivateKey struct {
|
||||
*rsa.PrivateKey
|
||||
}
|
||||
|
||||
func (r *rsaPrivateKey) PublicKey() PublicKey {
|
||||
return (*rsaPublicKey)(&r.PrivateKey.PublicKey)
|
||||
}
|
||||
|
||||
func (r *rsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
h := crypto.SHA1.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
blob, err := rsa.SignPKCS1v15(rand, r.PrivateKey, crypto.SHA1, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Signature{
|
||||
Format: r.PublicKey().Type(),
|
||||
Blob: blob,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dsaPublicKey dsa.PublicKey
|
||||
|
||||
func (r *dsaPublicKey) Type() string {
|
||||
return "ssh-dss"
|
||||
}
|
||||
|
||||
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
||||
func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
var w struct {
|
||||
P, Q, G, Y *big.Int
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
if err := Unmarshal(in, &w); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key := &dsaPublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: w.P,
|
||||
Q: w.Q,
|
||||
G: w.G,
|
||||
},
|
||||
Y: w.Y,
|
||||
}
|
||||
return key, w.Rest, nil
|
||||
}
|
||||
|
||||
func (k *dsaPublicKey) Marshal() []byte {
|
||||
w := struct {
|
||||
Name string
|
||||
P, Q, G, Y *big.Int
|
||||
}{
|
||||
k.Type(),
|
||||
k.P,
|
||||
k.Q,
|
||||
k.G,
|
||||
k.Y,
|
||||
}
|
||||
|
||||
return Marshal(&w)
|
||||
}
|
||||
|
||||
func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != k.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||
}
|
||||
h := crypto.SHA1.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
// Per RFC 4253, section 6.6,
|
||||
// The value for 'dss_signature_blob' is encoded as a string containing
|
||||
// r, followed by s (which are 160-bit integers, without lengths or
|
||||
// padding, unsigned, and in network byte order).
|
||||
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
|
||||
if len(sig.Blob) != 40 {
|
||||
return errors.New("ssh: DSA signature parse error")
|
||||
}
|
||||
r := new(big.Int).SetBytes(sig.Blob[:20])
|
||||
s := new(big.Int).SetBytes(sig.Blob[20:])
|
||||
if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("ssh: signature did not verify")
|
||||
}
|
||||
|
||||
type dsaPrivateKey struct {
|
||||
*dsa.PrivateKey
|
||||
}
|
||||
|
||||
func (k *dsaPrivateKey) PublicKey() PublicKey {
|
||||
return (*dsaPublicKey)(&k.PrivateKey.PublicKey)
|
||||
}
|
||||
|
||||
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
h := crypto.SHA1.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := make([]byte, 40)
|
||||
rb := r.Bytes()
|
||||
sb := s.Bytes()
|
||||
|
||||
copy(sig[20-len(rb):20], rb)
|
||||
copy(sig[40-len(sb):], sb)
|
||||
|
||||
return &Signature{
|
||||
Format: k.PublicKey().Type(),
|
||||
Blob: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ecdsaPublicKey ecdsa.PublicKey
|
||||
|
||||
func (key *ecdsaPublicKey) Type() string {
|
||||
return "ecdsa-sha2-" + key.nistID()
|
||||
}
|
||||
|
||||
func (key *ecdsaPublicKey) nistID() string {
|
||||
switch key.Params().BitSize {
|
||||
case 256:
|
||||
return "nistp256"
|
||||
case 384:
|
||||
return "nistp384"
|
||||
case 521:
|
||||
return "nistp521"
|
||||
}
|
||||
panic("ssh: unsupported ecdsa key size")
|
||||
}
|
||||
|
||||
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
||||
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
||||
}
|
||||
|
||||
// ecHash returns the hash to match the given elliptic curve, see RFC
|
||||
// 5656, section 6.2.1
|
||||
func ecHash(curve elliptic.Curve) crypto.Hash {
|
||||
bitSize := curve.Params().BitSize
|
||||
switch {
|
||||
case bitSize <= 256:
|
||||
return crypto.SHA256
|
||||
case bitSize <= 384:
|
||||
return crypto.SHA384
|
||||
}
|
||||
return crypto.SHA512
|
||||
}
|
||||
|
||||
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
||||
func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
var w struct {
|
||||
Curve string
|
||||
KeyBytes []byte
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
if err := Unmarshal(in, &w); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key := new(ecdsa.PublicKey)
|
||||
|
||||
switch w.Curve {
|
||||
case "nistp256":
|
||||
key.Curve = elliptic.P256()
|
||||
case "nistp384":
|
||||
key.Curve = elliptic.P384()
|
||||
case "nistp521":
|
||||
key.Curve = elliptic.P521()
|
||||
default:
|
||||
return nil, nil, errors.New("ssh: unsupported curve")
|
||||
}
|
||||
|
||||
key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes)
|
||||
if key.X == nil || key.Y == nil {
|
||||
return nil, nil, errors.New("ssh: invalid curve point")
|
||||
}
|
||||
return (*ecdsaPublicKey)(key), w.Rest, nil
|
||||
}
|
||||
|
||||
func (key *ecdsaPublicKey) Marshal() []byte {
|
||||
// See RFC 5656, section 3.1.
|
||||
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
||||
w := struct {
|
||||
Name string
|
||||
ID string
|
||||
Key []byte
|
||||
}{
|
||||
key.Type(),
|
||||
key.nistID(),
|
||||
keyBytes,
|
||||
}
|
||||
|
||||
return Marshal(&w)
|
||||
}
|
||||
|
||||
func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != key.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
||||
}
|
||||
|
||||
h := ecHash(key.Curve).New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
// Per RFC 5656, section 3.1.2,
|
||||
// The ecdsa_signature_blob value has the following specific encoding:
|
||||
// mpint r
|
||||
// mpint s
|
||||
var ecSig struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
if err := Unmarshal(sig.Blob, &ecSig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("ssh: signature did not verify")
|
||||
}
|
||||
|
||||
type ecdsaPrivateKey struct {
|
||||
*ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
func (k *ecdsaPrivateKey) PublicKey() PublicKey {
|
||||
return (*ecdsaPublicKey)(&k.PrivateKey.PublicKey)
|
||||
}
|
||||
|
||||
func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
h := ecHash(k.PrivateKey.PublicKey.Curve).New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
r, s, err := ecdsa.Sign(rand, k.PrivateKey, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := make([]byte, intLength(r)+intLength(s))
|
||||
rest := marshalInt(sig, r)
|
||||
marshalInt(rest, s)
|
||||
return &Signature{
|
||||
Format: k.PublicKey().Type(),
|
||||
Blob: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewSignerFromKey takes a pointer to rsa, dsa or ecdsa PrivateKey
|
||||
// returns a corresponding Signer instance. EC keys should use P256,
|
||||
// P384 or P521.
|
||||
func NewSignerFromKey(k interface{}) (Signer, error) {
|
||||
var sshKey Signer
|
||||
switch t := k.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
sshKey = &rsaPrivateKey{t}
|
||||
case *dsa.PrivateKey:
|
||||
sshKey = &dsaPrivateKey{t}
|
||||
case *ecdsa.PrivateKey:
|
||||
if !supportedEllipticCurve(t.Curve) {
|
||||
return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
|
||||
}
|
||||
|
||||
sshKey = &ecdsaPrivateKey{t}
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
|
||||
}
|
||||
return sshKey, nil
|
||||
}
|
||||
|
||||
// NewPublicKey takes a pointer to rsa, dsa or ecdsa PublicKey
|
||||
// and returns a corresponding ssh PublicKey instance. EC keys should use P256, P384 or P521.
|
||||
func NewPublicKey(k interface{}) (PublicKey, error) {
|
||||
var sshKey PublicKey
|
||||
switch t := k.(type) {
|
||||
case *rsa.PublicKey:
|
||||
sshKey = (*rsaPublicKey)(t)
|
||||
case *ecdsa.PublicKey:
|
||||
if !supportedEllipticCurve(t.Curve) {
|
||||
return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
|
||||
}
|
||||
sshKey = (*ecdsaPublicKey)(t)
|
||||
case *dsa.PublicKey:
|
||||
sshKey = (*dsaPublicKey)(t)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
|
||||
}
|
||||
return sshKey, nil
|
||||
}
|
||||
|
||||
// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports
|
||||
// the same keys as ParseRawPrivateKey.
|
||||
func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
||||
key, err := ParseRawPrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
||||
// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys.
|
||||
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("ssh: no key found")
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
case "DSA PRIVATE KEY":
|
||||
return ParseDSAPrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
|
||||
// specified by the OpenSSL DSA man page.
|
||||
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
||||
var k struct {
|
||||
Version int
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
G *big.Int
|
||||
Priv *big.Int
|
||||
Pub *big.Int
|
||||
}
|
||||
rest, err := asn1.Unmarshal(der, &k)
|
||||
if err != nil {
|
||||
return nil, errors.New("ssh: failed to parse DSA key: " + err.Error())
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
return nil, errors.New("ssh: garbage after DSA key")
|
||||
}
|
||||
|
||||
return &dsa.PrivateKey{
|
||||
PublicKey: dsa.PublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: k.P,
|
||||
Q: k.Q,
|
||||
G: k.G,
|
||||
},
|
||||
Y: k.Priv,
|
||||
},
|
||||
X: k.Pub,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
func rawKey(pub PublicKey) interface{} {
|
||||
switch k := pub.(type) {
|
||||
case *rsaPublicKey:
|
||||
return (*rsa.PublicKey)(k)
|
||||
case *dsaPublicKey:
|
||||
return (*dsa.PublicKey)(k)
|
||||
case *ecdsaPublicKey:
|
||||
return (*ecdsa.PublicKey)(k)
|
||||
case *Certificate:
|
||||
return k
|
||||
}
|
||||
panic("unknown key type")
|
||||
}
|
||||
|
||||
func TestKeyMarshalParse(t *testing.T) {
|
||||
for _, priv := range testSigners {
|
||||
pub := priv.PublicKey()
|
||||
roundtrip, err := ParsePublicKey(pub.Marshal())
|
||||
if err != nil {
|
||||
t.Errorf("ParsePublicKey(%T): %v", pub, err)
|
||||
}
|
||||
|
||||
k1 := rawKey(pub)
|
||||
k2 := rawKey(roundtrip)
|
||||
|
||||
if !reflect.DeepEqual(k1, k2) {
|
||||
t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedCurves(t *testing.T) {
|
||||
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
|
||||
if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P256") {
|
||||
t.Fatalf("NewPrivateKey should not succeed with P224, got: %v", err)
|
||||
}
|
||||
|
||||
if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P256") {
|
||||
t.Fatalf("NewPublicKey should not succeed with P224, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPublicKey(t *testing.T) {
|
||||
for _, k := range testSigners {
|
||||
raw := rawKey(k.PublicKey())
|
||||
// Skip certificates, as NewPublicKey does not support them.
|
||||
if _, ok := raw.(*Certificate); ok {
|
||||
continue
|
||||
}
|
||||
pub, err := NewPublicKey(raw)
|
||||
if err != nil {
|
||||
t.Errorf("NewPublicKey(%#v): %v", raw, err)
|
||||
}
|
||||
if !reflect.DeepEqual(k.PublicKey(), pub) {
|
||||
t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySignVerify(t *testing.T) {
|
||||
for _, priv := range testSigners {
|
||||
pub := priv.PublicKey()
|
||||
|
||||
data := []byte("sign me")
|
||||
sig, err := priv.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(%T): %v", priv, err)
|
||||
}
|
||||
|
||||
if err := pub.Verify(data, sig); err != nil {
|
||||
t.Errorf("publicKey.Verify(%T): %v", priv, err)
|
||||
}
|
||||
sig.Blob[5]++
|
||||
if err := pub.Verify(data, sig); err == nil {
|
||||
t.Errorf("publicKey.Verify on broken sig did not fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRSAPrivateKey(t *testing.T) {
|
||||
key := testPrivateKeys["rsa"]
|
||||
|
||||
rsa, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
|
||||
}
|
||||
|
||||
if err := rsa.Validate(); err != nil {
|
||||
t.Errorf("Validate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseECPrivateKey(t *testing.T) {
|
||||
key := testPrivateKeys["ecdsa"]
|
||||
|
||||
ecKey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey)
|
||||
}
|
||||
|
||||
if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
|
||||
t.Fatalf("public key does not validate.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDSA(t *testing.T) {
|
||||
// We actually exercise the ParsePrivateKey codepath here, as opposed to
|
||||
// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
|
||||
// uses.
|
||||
s, err := ParsePrivateKey(testdata.PEMBytes["dsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKey returned error: %s", err)
|
||||
}
|
||||
|
||||
data := []byte("sign me")
|
||||
sig, err := s.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Fatalf("dsa.Sign: %v", err)
|
||||
}
|
||||
|
||||
if err := s.PublicKey().Verify(data, sig); err != nil {
|
||||
t.Errorf("Verify failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for authorized_keys parsing.
|
||||
|
||||
// getTestKey returns a public key, and its base64 encoding.
|
||||
func getTestKey() (PublicKey, string) {
|
||||
k := testPublicKeys["rsa"]
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
e := base64.NewEncoder(base64.StdEncoding, b)
|
||||
e.Write(k.Marshal())
|
||||
e.Close()
|
||||
|
||||
return k, b.String()
|
||||
}
|
||||
|
||||
func TestMarshalParsePublicKey(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized)
|
||||
|
||||
authKeys := MarshalAuthorizedKey(pub)
|
||||
actualFields := strings.Fields(string(authKeys))
|
||||
if len(actualFields) == 0 {
|
||||
t.Fatalf("failed authKeys: %v", authKeys)
|
||||
}
|
||||
|
||||
// drop the comment
|
||||
expectedFields := strings.Fields(line)[0:2]
|
||||
|
||||
if !reflect.DeepEqual(actualFields, expectedFields) {
|
||||
t.Errorf("got %v, expected %v", actualFields, expectedFields)
|
||||
}
|
||||
|
||||
actPub, _, _, _, err := ParseAuthorizedKey([]byte(line))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse %v: %v", line, err)
|
||||
}
|
||||
if !reflect.DeepEqual(actPub, pub) {
|
||||
t.Errorf("got %v, expected %v", actPub, pub)
|
||||
}
|
||||
}
|
||||
|
||||
type authResult struct {
|
||||
pubKey PublicKey
|
||||
options []string
|
||||
comments string
|
||||
rest string
|
||||
ok bool
|
||||
}
|
||||
|
||||
func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) {
|
||||
rest := authKeys
|
||||
var values []authResult
|
||||
for len(rest) > 0 {
|
||||
var r authResult
|
||||
var err error
|
||||
r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
|
||||
r.ok = (err == nil)
|
||||
t.Log(err)
|
||||
r.rest = string(rest)
|
||||
values = append(values, r)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(values, expected) {
|
||||
t.Errorf("got %#v, expected %#v", values, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizedKeyBasic(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
line := "ssh-rsa " + pubSerialized + " user@host"
|
||||
testAuthorizedKeys(t, []byte(line),
|
||||
[]authResult{
|
||||
{pub, nil, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithOptions := []string{
|
||||
`# comments to ignore before any keys...`,
|
||||
``,
|
||||
`env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`,
|
||||
`# comments to ignore, along with a blank line`,
|
||||
``,
|
||||
`env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`,
|
||||
``,
|
||||
`# more comments, plus a invalid entry`,
|
||||
`ssh-rsa data-that-will-not-parse user@host3`,
|
||||
}
|
||||
for _, eol := range []string{"\n", "\r\n"} {
|
||||
authOptions := strings.Join(authWithOptions, eol)
|
||||
rest2 := strings.Join(authWithOptions[3:], eol)
|
||||
rest3 := strings.Join(authWithOptions[6:], eol)
|
||||
testAuthorizedKeys(t, []byte(authOptions), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
|
||||
{pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
|
||||
{nil, nil, "", "", false},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
|
||||
testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithQuotedCommaInEnv(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
|
||||
testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
|
||||
authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
|
||||
testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{
|
||||
{pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
|
||||
})
|
||||
|
||||
testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{
|
||||
{pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithInvalidSpace(t *testing.T) {
|
||||
_, pubSerialized := getTestKey()
|
||||
authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
|
||||
#more to follow but still no valid keys`)
|
||||
testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{
|
||||
{nil, nil, "", "", false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithMissingQuote(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
|
||||
env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)
|
||||
|
||||
testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInvalidEntry(t *testing.T) {
|
||||
authInvalid := []byte(`ssh-rsa`)
|
||||
_, _, _, _, err := ParseAuthorizedKey(authInvalid)
|
||||
if err == nil {
|
||||
t.Errorf("got valid entry for %q", authInvalid)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Message authentication support
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
)
|
||||
|
||||
type macMode struct {
|
||||
keySize int
|
||||
new func(key []byte) hash.Hash
|
||||
}
|
||||
|
||||
// truncatingMAC wraps around a hash.Hash and truncates the output digest to
|
||||
// a given size.
|
||||
type truncatingMAC struct {
|
||||
length int
|
||||
hmac hash.Hash
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Write(data []byte) (int, error) {
|
||||
return t.hmac.Write(data)
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Sum(in []byte) []byte {
|
||||
out := t.hmac.Sum(in)
|
||||
return out[:len(in)+t.length]
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Reset() {
|
||||
t.hmac.Reset()
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Size() int {
|
||||
return t.length
|
||||
}
|
||||
|
||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||
|
||||
var macModes = map[string]*macMode{
|
||||
"hmac-sha2-256": {32, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
"hmac-sha1": {20, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha1.New, key)
|
||||
}},
|
||||
"hmac-sha1-96": {20, func(key []byte) hash.Hash {
|
||||
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
||||
}},
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// An in-memory packetConn. It is safe to call Close and writePacket
|
||||
// from different goroutines.
|
||||
type memTransport struct {
|
||||
eof bool
|
||||
pending [][]byte
|
||||
write *memTransport
|
||||
sync.Mutex
|
||||
*sync.Cond
|
||||
}
|
||||
|
||||
func (t *memTransport) readPacket() ([]byte, error) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
for {
|
||||
if len(t.pending) > 0 {
|
||||
r := t.pending[0]
|
||||
t.pending = t.pending[1:]
|
||||
return r, nil
|
||||
}
|
||||
if t.eof {
|
||||
return nil, io.EOF
|
||||
}
|
||||
t.Cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *memTransport) closeSelf() error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.eof {
|
||||
return io.EOF
|
||||
}
|
||||
t.eof = true
|
||||
t.Cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *memTransport) Close() error {
|
||||
err := t.write.closeSelf()
|
||||
t.closeSelf()
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *memTransport) writePacket(p []byte) error {
|
||||
t.write.Lock()
|
||||
defer t.write.Unlock()
|
||||
if t.write.eof {
|
||||
return io.EOF
|
||||
}
|
||||
c := make([]byte, len(p))
|
||||
copy(c, p)
|
||||
t.write.pending = append(t.write.pending, c)
|
||||
t.write.Cond.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func memPipe() (a, b packetConn) {
|
||||
t1 := memTransport{}
|
||||
t2 := memTransport{}
|
||||
t1.write = &t2
|
||||
t2.write = &t1
|
||||
t1.Cond = sync.NewCond(&t1.Mutex)
|
||||
t2.Cond = sync.NewCond(&t2.Mutex)
|
||||
return &t1, &t2
|
||||
}
|
||||
|
||||
func TestMemPipe(t *testing.T) {
|
||||
a, b := memPipe()
|
||||
if err := a.writePacket([]byte{42}); err != nil {
|
||||
t.Fatalf("writePacket: %v", err)
|
||||
}
|
||||
if err := a.Close(); err != nil {
|
||||
t.Fatal("Close: ", err)
|
||||
}
|
||||
p, err := b.readPacket()
|
||||
if err != nil {
|
||||
t.Fatal("readPacket: ", err)
|
||||
}
|
||||
if len(p) != 1 || p[0] != 42 {
|
||||
t.Fatalf("got %v, want {42}", p)
|
||||
}
|
||||
p, err = b.readPacket()
|
||||
if err != io.EOF {
|
||||
t.Fatalf("got %v, %v, want EOF", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleClose(t *testing.T) {
|
||||
a, _ := memPipe()
|
||||
err := a.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Close: %v", err)
|
||||
}
|
||||
err = a.Close()
|
||||
if err != io.EOF {
|
||||
t.Errorf("expect EOF on double close.")
|
||||
}
|
||||
}
|
||||
@@ -1,725 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// These are SSH message type numbers. They are scattered around several
|
||||
// documents but many were taken from [SSH-PARAMETERS].
|
||||
const (
|
||||
msgIgnore = 2
|
||||
msgUnimplemented = 3
|
||||
msgDebug = 4
|
||||
msgNewKeys = 21
|
||||
|
||||
// Standard authentication messages
|
||||
msgUserAuthSuccess = 52
|
||||
msgUserAuthBanner = 53
|
||||
)
|
||||
|
||||
// SSH messages:
|
||||
//
|
||||
// These structures mirror the wire format of the corresponding SSH messages.
|
||||
// They are marshaled using reflection with the marshal and unmarshal functions
|
||||
// in this file. The only wrinkle is that a final member of type []byte with a
|
||||
// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
|
||||
|
||||
// See RFC 4253, section 11.1.
|
||||
const msgDisconnect = 1
|
||||
|
||||
// disconnectMsg is the message that signals a disconnect. It is also
|
||||
// the error type returned from mux.Wait()
|
||||
type disconnectMsg struct {
|
||||
Reason uint32 `sshtype:"1"`
|
||||
Message string
|
||||
Language string
|
||||
}
|
||||
|
||||
func (d *disconnectMsg) Error() string {
|
||||
return fmt.Sprintf("ssh: disconnect reason %d: %s", d.Reason, d.Message)
|
||||
}
|
||||
|
||||
// See RFC 4253, section 7.1.
|
||||
const msgKexInit = 20
|
||||
|
||||
type kexInitMsg struct {
|
||||
Cookie [16]byte `sshtype:"20"`
|
||||
KexAlgos []string
|
||||
ServerHostKeyAlgos []string
|
||||
CiphersClientServer []string
|
||||
CiphersServerClient []string
|
||||
MACsClientServer []string
|
||||
MACsServerClient []string
|
||||
CompressionClientServer []string
|
||||
CompressionServerClient []string
|
||||
LanguagesClientServer []string
|
||||
LanguagesServerClient []string
|
||||
FirstKexFollows bool
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
// See RFC 4253, section 8.
|
||||
|
||||
// Diffie-Helman
|
||||
const msgKexDHInit = 30
|
||||
|
||||
type kexDHInitMsg struct {
|
||||
X *big.Int `sshtype:"30"`
|
||||
}
|
||||
|
||||
const msgKexECDHInit = 30
|
||||
|
||||
type kexECDHInitMsg struct {
|
||||
ClientPubKey []byte `sshtype:"30"`
|
||||
}
|
||||
|
||||
const msgKexECDHReply = 31
|
||||
|
||||
type kexECDHReplyMsg struct {
|
||||
HostKey []byte `sshtype:"31"`
|
||||
EphemeralPubKey []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
const msgKexDHReply = 31
|
||||
|
||||
type kexDHReplyMsg struct {
|
||||
HostKey []byte `sshtype:"31"`
|
||||
Y *big.Int
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// See RFC 4253, section 10.
|
||||
const msgServiceRequest = 5
|
||||
|
||||
type serviceRequestMsg struct {
|
||||
Service string `sshtype:"5"`
|
||||
}
|
||||
|
||||
// See RFC 4253, section 10.
|
||||
const msgServiceAccept = 6
|
||||
|
||||
type serviceAcceptMsg struct {
|
||||
Service string `sshtype:"6"`
|
||||
}
|
||||
|
||||
// See RFC 4252, section 5.
|
||||
const msgUserAuthRequest = 50
|
||||
|
||||
type userAuthRequestMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Payload []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4252, section 5.1
|
||||
const msgUserAuthFailure = 51
|
||||
|
||||
type userAuthFailureMsg struct {
|
||||
Methods []string `sshtype:"51"`
|
||||
PartialSuccess bool
|
||||
}
|
||||
|
||||
// See RFC 4256, section 3.2
|
||||
const msgUserAuthInfoRequest = 60
|
||||
const msgUserAuthInfoResponse = 61
|
||||
|
||||
type userAuthInfoRequestMsg struct {
|
||||
User string `sshtype:"60"`
|
||||
Instruction string
|
||||
DeprecatedLanguage string
|
||||
NumPrompts uint32
|
||||
Prompts []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.1.
|
||||
const msgChannelOpen = 90
|
||||
|
||||
type channelOpenMsg struct {
|
||||
ChanType string `sshtype:"90"`
|
||||
PeersId uint32
|
||||
PeersWindow uint32
|
||||
MaxPacketSize uint32
|
||||
TypeSpecificData []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
const msgChannelExtendedData = 95
|
||||
const msgChannelData = 94
|
||||
|
||||
// See RFC 4254, section 5.1.
|
||||
const msgChannelOpenConfirm = 91
|
||||
|
||||
type channelOpenConfirmMsg struct {
|
||||
PeersId uint32 `sshtype:"91"`
|
||||
MyId uint32
|
||||
MyWindow uint32
|
||||
MaxPacketSize uint32
|
||||
TypeSpecificData []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.1.
|
||||
const msgChannelOpenFailure = 92
|
||||
|
||||
type channelOpenFailureMsg struct {
|
||||
PeersId uint32 `sshtype:"92"`
|
||||
Reason RejectionReason
|
||||
Message string
|
||||
Language string
|
||||
}
|
||||
|
||||
const msgChannelRequest = 98
|
||||
|
||||
type channelRequestMsg struct {
|
||||
PeersId uint32 `sshtype:"98"`
|
||||
Request string
|
||||
WantReply bool
|
||||
RequestSpecificData []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.4.
|
||||
const msgChannelSuccess = 99
|
||||
|
||||
type channelRequestSuccessMsg struct {
|
||||
PeersId uint32 `sshtype:"99"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.4.
|
||||
const msgChannelFailure = 100
|
||||
|
||||
type channelRequestFailureMsg struct {
|
||||
PeersId uint32 `sshtype:"100"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.3
|
||||
const msgChannelClose = 97
|
||||
|
||||
type channelCloseMsg struct {
|
||||
PeersId uint32 `sshtype:"97"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.3
|
||||
const msgChannelEOF = 96
|
||||
|
||||
type channelEOFMsg struct {
|
||||
PeersId uint32 `sshtype:"96"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
const msgGlobalRequest = 80
|
||||
|
||||
type globalRequestMsg struct {
|
||||
Type string `sshtype:"80"`
|
||||
WantReply bool
|
||||
Data []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
const msgRequestSuccess = 81
|
||||
|
||||
type globalRequestSuccessMsg struct {
|
||||
Data []byte `ssh:"rest" sshtype:"81"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
const msgRequestFailure = 82
|
||||
|
||||
type globalRequestFailureMsg struct {
|
||||
Data []byte `ssh:"rest" sshtype:"82"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.2
|
||||
const msgChannelWindowAdjust = 93
|
||||
|
||||
type windowAdjustMsg struct {
|
||||
PeersId uint32 `sshtype:"93"`
|
||||
AdditionalBytes uint32
|
||||
}
|
||||
|
||||
// See RFC 4252, section 7
|
||||
const msgUserAuthPubKeyOk = 60
|
||||
|
||||
type userAuthPubKeyOkMsg struct {
|
||||
Algo string `sshtype:"60"`
|
||||
PubKey []byte
|
||||
}
|
||||
|
||||
// typeTag returns the type byte for the given type. The type should
|
||||
// be struct.
|
||||
func typeTag(structType reflect.Type) byte {
|
||||
var tag byte
|
||||
var tagStr string
|
||||
tagStr = structType.Field(0).Tag.Get("sshtype")
|
||||
i, err := strconv.Atoi(tagStr)
|
||||
if err == nil {
|
||||
tag = byte(i)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func fieldError(t reflect.Type, field int, problem string) error {
|
||||
if problem != "" {
|
||||
problem = ": " + problem
|
||||
}
|
||||
return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem)
|
||||
}
|
||||
|
||||
var errShortRead = errors.New("ssh: short read")
|
||||
|
||||
// Unmarshal parses data in SSH wire format into a structure. The out
|
||||
// argument should be a pointer to struct. If the first member of the
|
||||
// struct has the "sshtype" tag set to a number in decimal, the packet
|
||||
// must start that number. In case of error, Unmarshal returns a
|
||||
// ParseError or UnexpectedMessageError.
|
||||
func Unmarshal(data []byte, out interface{}) error {
|
||||
v := reflect.ValueOf(out).Elem()
|
||||
structType := v.Type()
|
||||
expectedType := typeTag(structType)
|
||||
if len(data) == 0 {
|
||||
return parseError(expectedType)
|
||||
}
|
||||
if expectedType > 0 {
|
||||
if data[0] != expectedType {
|
||||
return unexpectedMessageError(expectedType, data[0])
|
||||
}
|
||||
data = data[1:]
|
||||
}
|
||||
|
||||
var ok bool
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
t := field.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
if len(data) < 1 {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetBool(data[0] != 0)
|
||||
data = data[1:]
|
||||
case reflect.Array:
|
||||
if t.Elem().Kind() != reflect.Uint8 {
|
||||
return fieldError(structType, i, "array of unsupported type")
|
||||
}
|
||||
if len(data) < t.Len() {
|
||||
return errShortRead
|
||||
}
|
||||
for j, n := 0, t.Len(); j < n; j++ {
|
||||
field.Index(j).Set(reflect.ValueOf(data[j]))
|
||||
}
|
||||
data = data[t.Len():]
|
||||
case reflect.Uint64:
|
||||
var u64 uint64
|
||||
if u64, data, ok = parseUint64(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetUint(u64)
|
||||
case reflect.Uint32:
|
||||
var u32 uint32
|
||||
if u32, data, ok = parseUint32(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetUint(uint64(u32))
|
||||
case reflect.Uint8:
|
||||
if len(data) < 1 {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetUint(uint64(data[0]))
|
||||
data = data[1:]
|
||||
case reflect.String:
|
||||
var s []byte
|
||||
if s, data, ok = parseString(data); !ok {
|
||||
return fieldError(structType, i, "")
|
||||
}
|
||||
field.SetString(string(s))
|
||||
case reflect.Slice:
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if structType.Field(i).Tag.Get("ssh") == "rest" {
|
||||
field.Set(reflect.ValueOf(data))
|
||||
data = nil
|
||||
} else {
|
||||
var s []byte
|
||||
if s, data, ok = parseString(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.Set(reflect.ValueOf(s))
|
||||
}
|
||||
case reflect.String:
|
||||
var nl []string
|
||||
if nl, data, ok = parseNameList(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.Set(reflect.ValueOf(nl))
|
||||
default:
|
||||
return fieldError(structType, i, "slice of unsupported type")
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t == bigIntType {
|
||||
var n *big.Int
|
||||
if n, data, ok = parseInt(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.Set(reflect.ValueOf(n))
|
||||
} else {
|
||||
return fieldError(structType, i, "pointer to unsupported type")
|
||||
}
|
||||
default:
|
||||
return fieldError(structType, i, "unsupported type")
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) != 0 {
|
||||
return parseError(expectedType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal serializes the message in msg to SSH wire format. The msg
|
||||
// argument should be a struct or pointer to struct. If the first
|
||||
// member has the "sshtype" tag set to a number in decimal, that
|
||||
// number is prepended to the result. If the last of member has the
|
||||
// "ssh" tag set to "rest", its contents are appended to the output.
|
||||
func Marshal(msg interface{}) []byte {
|
||||
out := make([]byte, 0, 64)
|
||||
return marshalStruct(out, msg)
|
||||
}
|
||||
|
||||
func marshalStruct(out []byte, msg interface{}) []byte {
|
||||
v := reflect.Indirect(reflect.ValueOf(msg))
|
||||
msgType := typeTag(v.Type())
|
||||
if msgType > 0 {
|
||||
out = append(out, msgType)
|
||||
}
|
||||
|
||||
for i, n := 0, v.NumField(); i < n; i++ {
|
||||
field := v.Field(i)
|
||||
switch t := field.Type(); t.Kind() {
|
||||
case reflect.Bool:
|
||||
var v uint8
|
||||
if field.Bool() {
|
||||
v = 1
|
||||
}
|
||||
out = append(out, v)
|
||||
case reflect.Array:
|
||||
if t.Elem().Kind() != reflect.Uint8 {
|
||||
panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface()))
|
||||
}
|
||||
for j, l := 0, t.Len(); j < l; j++ {
|
||||
out = append(out, uint8(field.Index(j).Uint()))
|
||||
}
|
||||
case reflect.Uint32:
|
||||
out = appendU32(out, uint32(field.Uint()))
|
||||
case reflect.Uint64:
|
||||
out = appendU64(out, uint64(field.Uint()))
|
||||
case reflect.Uint8:
|
||||
out = append(out, uint8(field.Uint()))
|
||||
case reflect.String:
|
||||
s := field.String()
|
||||
out = appendInt(out, len(s))
|
||||
out = append(out, s...)
|
||||
case reflect.Slice:
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if v.Type().Field(i).Tag.Get("ssh") != "rest" {
|
||||
out = appendInt(out, field.Len())
|
||||
}
|
||||
out = append(out, field.Bytes()...)
|
||||
case reflect.String:
|
||||
offset := len(out)
|
||||
out = appendU32(out, 0)
|
||||
if n := field.Len(); n > 0 {
|
||||
for j := 0; j < n; j++ {
|
||||
f := field.Index(j)
|
||||
if j != 0 {
|
||||
out = append(out, ',')
|
||||
}
|
||||
out = append(out, f.String()...)
|
||||
}
|
||||
// overwrite length value
|
||||
binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface()))
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t == bigIntType {
|
||||
var n *big.Int
|
||||
nValue := reflect.ValueOf(&n)
|
||||
nValue.Elem().Set(field)
|
||||
needed := intLength(n)
|
||||
oldLength := len(out)
|
||||
|
||||
if cap(out)-len(out) < needed {
|
||||
newOut := make([]byte, len(out), 2*(len(out)+needed))
|
||||
copy(newOut, out)
|
||||
out = newOut
|
||||
}
|
||||
out = out[:oldLength+needed]
|
||||
marshalInt(out[oldLength:], n)
|
||||
} else {
|
||||
panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var bigOne = big.NewInt(1)
|
||||
|
||||
func parseString(in []byte) (out, rest []byte, ok bool) {
|
||||
if len(in) < 4 {
|
||||
return
|
||||
}
|
||||
length := binary.BigEndian.Uint32(in)
|
||||
in = in[4:]
|
||||
if uint32(len(in)) < length {
|
||||
return
|
||||
}
|
||||
out = in[:length]
|
||||
rest = in[length:]
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
comma = []byte{','}
|
||||
emptyNameList = []string{}
|
||||
)
|
||||
|
||||
func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
|
||||
contents, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(contents) == 0 {
|
||||
out = emptyNameList
|
||||
return
|
||||
}
|
||||
parts := bytes.Split(contents, comma)
|
||||
out = make([]string, len(parts))
|
||||
for i, part := range parts {
|
||||
out[i] = string(part)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
|
||||
contents, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
out = new(big.Int)
|
||||
|
||||
if len(contents) > 0 && contents[0]&0x80 == 0x80 {
|
||||
// This is a negative number
|
||||
notBytes := make([]byte, len(contents))
|
||||
for i := range notBytes {
|
||||
notBytes[i] = ^contents[i]
|
||||
}
|
||||
out.SetBytes(notBytes)
|
||||
out.Add(out, bigOne)
|
||||
out.Neg(out)
|
||||
} else {
|
||||
// Positive number
|
||||
out.SetBytes(contents)
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func parseUint32(in []byte) (uint32, []byte, bool) {
|
||||
if len(in) < 4 {
|
||||
return 0, nil, false
|
||||
}
|
||||
return binary.BigEndian.Uint32(in), in[4:], true
|
||||
}
|
||||
|
||||
func parseUint64(in []byte) (uint64, []byte, bool) {
|
||||
if len(in) < 8 {
|
||||
return 0, nil, false
|
||||
}
|
||||
return binary.BigEndian.Uint64(in), in[8:], true
|
||||
}
|
||||
|
||||
func intLength(n *big.Int) int {
|
||||
length := 4 /* length bytes */
|
||||
if n.Sign() < 0 {
|
||||
nMinus1 := new(big.Int).Neg(n)
|
||||
nMinus1.Sub(nMinus1, bigOne)
|
||||
bitLen := nMinus1.BitLen()
|
||||
if bitLen%8 == 0 {
|
||||
// The number will need 0xff padding
|
||||
length++
|
||||
}
|
||||
length += (bitLen + 7) / 8
|
||||
} else if n.Sign() == 0 {
|
||||
// A zero is the zero length string
|
||||
} else {
|
||||
bitLen := n.BitLen()
|
||||
if bitLen%8 == 0 {
|
||||
// The number will need 0x00 padding
|
||||
length++
|
||||
}
|
||||
length += (bitLen + 7) / 8
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
func marshalUint32(to []byte, n uint32) []byte {
|
||||
binary.BigEndian.PutUint32(to, n)
|
||||
return to[4:]
|
||||
}
|
||||
|
||||
func marshalUint64(to []byte, n uint64) []byte {
|
||||
binary.BigEndian.PutUint64(to, n)
|
||||
return to[8:]
|
||||
}
|
||||
|
||||
func marshalInt(to []byte, n *big.Int) []byte {
|
||||
lengthBytes := to
|
||||
to = to[4:]
|
||||
length := 0
|
||||
|
||||
if n.Sign() < 0 {
|
||||
// A negative number has to be converted to two's-complement
|
||||
// form. So we'll subtract 1 and invert. If the
|
||||
// most-significant-bit isn't set then we'll need to pad the
|
||||
// beginning with 0xff in order to keep the number negative.
|
||||
nMinus1 := new(big.Int).Neg(n)
|
||||
nMinus1.Sub(nMinus1, bigOne)
|
||||
bytes := nMinus1.Bytes()
|
||||
for i := range bytes {
|
||||
bytes[i] ^= 0xff
|
||||
}
|
||||
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
||||
to[0] = 0xff
|
||||
to = to[1:]
|
||||
length++
|
||||
}
|
||||
nBytes := copy(to, bytes)
|
||||
to = to[nBytes:]
|
||||
length += nBytes
|
||||
} else if n.Sign() == 0 {
|
||||
// A zero is the zero length string
|
||||
} else {
|
||||
bytes := n.Bytes()
|
||||
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
|
||||
// We'll have to pad this with a 0x00 in order to
|
||||
// stop it looking like a negative number.
|
||||
to[0] = 0
|
||||
to = to[1:]
|
||||
length++
|
||||
}
|
||||
nBytes := copy(to, bytes)
|
||||
to = to[nBytes:]
|
||||
length += nBytes
|
||||
}
|
||||
|
||||
lengthBytes[0] = byte(length >> 24)
|
||||
lengthBytes[1] = byte(length >> 16)
|
||||
lengthBytes[2] = byte(length >> 8)
|
||||
lengthBytes[3] = byte(length)
|
||||
return to
|
||||
}
|
||||
|
||||
func writeInt(w io.Writer, n *big.Int) {
|
||||
length := intLength(n)
|
||||
buf := make([]byte, length)
|
||||
marshalInt(buf, n)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func writeString(w io.Writer, s []byte) {
|
||||
var lengthBytes [4]byte
|
||||
lengthBytes[0] = byte(len(s) >> 24)
|
||||
lengthBytes[1] = byte(len(s) >> 16)
|
||||
lengthBytes[2] = byte(len(s) >> 8)
|
||||
lengthBytes[3] = byte(len(s))
|
||||
w.Write(lengthBytes[:])
|
||||
w.Write(s)
|
||||
}
|
||||
|
||||
func stringLength(n int) int {
|
||||
return 4 + n
|
||||
}
|
||||
|
||||
func marshalString(to []byte, s []byte) []byte {
|
||||
to[0] = byte(len(s) >> 24)
|
||||
to[1] = byte(len(s) >> 16)
|
||||
to[2] = byte(len(s) >> 8)
|
||||
to[3] = byte(len(s))
|
||||
to = to[4:]
|
||||
copy(to, s)
|
||||
return to[len(s):]
|
||||
}
|
||||
|
||||
var bigIntType = reflect.TypeOf((*big.Int)(nil))
|
||||
|
||||
// Decode a packet into its corresponding message.
|
||||
func decode(packet []byte) (interface{}, error) {
|
||||
var msg interface{}
|
||||
switch packet[0] {
|
||||
case msgDisconnect:
|
||||
msg = new(disconnectMsg)
|
||||
case msgServiceRequest:
|
||||
msg = new(serviceRequestMsg)
|
||||
case msgServiceAccept:
|
||||
msg = new(serviceAcceptMsg)
|
||||
case msgKexInit:
|
||||
msg = new(kexInitMsg)
|
||||
case msgKexDHInit:
|
||||
msg = new(kexDHInitMsg)
|
||||
case msgKexDHReply:
|
||||
msg = new(kexDHReplyMsg)
|
||||
case msgUserAuthRequest:
|
||||
msg = new(userAuthRequestMsg)
|
||||
case msgUserAuthFailure:
|
||||
msg = new(userAuthFailureMsg)
|
||||
case msgUserAuthPubKeyOk:
|
||||
msg = new(userAuthPubKeyOkMsg)
|
||||
case msgGlobalRequest:
|
||||
msg = new(globalRequestMsg)
|
||||
case msgRequestSuccess:
|
||||
msg = new(globalRequestSuccessMsg)
|
||||
case msgRequestFailure:
|
||||
msg = new(globalRequestFailureMsg)
|
||||
case msgChannelOpen:
|
||||
msg = new(channelOpenMsg)
|
||||
case msgChannelOpenConfirm:
|
||||
msg = new(channelOpenConfirmMsg)
|
||||
case msgChannelOpenFailure:
|
||||
msg = new(channelOpenFailureMsg)
|
||||
case msgChannelWindowAdjust:
|
||||
msg = new(windowAdjustMsg)
|
||||
case msgChannelEOF:
|
||||
msg = new(channelEOFMsg)
|
||||
case msgChannelClose:
|
||||
msg = new(channelCloseMsg)
|
||||
case msgChannelRequest:
|
||||
msg = new(channelRequestMsg)
|
||||
case msgChannelSuccess:
|
||||
msg = new(channelRequestSuccessMsg)
|
||||
case msgChannelFailure:
|
||||
msg = new(channelRequestFailureMsg)
|
||||
default:
|
||||
return nil, unexpectedMessageError(0, packet[0])
|
||||
}
|
||||
if err := Unmarshal(packet, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
var intLengthTests = []struct {
|
||||
val, length int
|
||||
}{
|
||||
{0, 4 + 0},
|
||||
{1, 4 + 1},
|
||||
{127, 4 + 1},
|
||||
{128, 4 + 2},
|
||||
{-1, 4 + 1},
|
||||
}
|
||||
|
||||
func TestIntLength(t *testing.T) {
|
||||
for _, test := range intLengthTests {
|
||||
v := new(big.Int).SetInt64(int64(test.val))
|
||||
length := intLength(v)
|
||||
if length != test.length {
|
||||
t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type msgAllTypes struct {
|
||||
Bool bool `sshtype:"21"`
|
||||
Array [16]byte
|
||||
Uint64 uint64
|
||||
Uint32 uint32
|
||||
Uint8 uint8
|
||||
String string
|
||||
Strings []string
|
||||
Bytes []byte
|
||||
Int *big.Int
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
func (t *msgAllTypes) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := &msgAllTypes{}
|
||||
m.Bool = rand.Intn(2) == 1
|
||||
randomBytes(m.Array[:], rand)
|
||||
m.Uint64 = uint64(rand.Int63n(1<<63 - 1))
|
||||
m.Uint32 = uint32(rand.Intn((1 << 31) - 1))
|
||||
m.Uint8 = uint8(rand.Intn(1 << 8))
|
||||
m.String = string(m.Array[:])
|
||||
m.Strings = randomNameList(rand)
|
||||
m.Bytes = m.Array[:]
|
||||
m.Int = randomInt(rand)
|
||||
m.Rest = m.Array[:]
|
||||
return reflect.ValueOf(m)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshal(t *testing.T) {
|
||||
rand := rand.New(rand.NewSource(0))
|
||||
iface := &msgAllTypes{}
|
||||
ty := reflect.ValueOf(iface).Type()
|
||||
|
||||
n := 100
|
||||
if testing.Short() {
|
||||
n = 5
|
||||
}
|
||||
for j := 0; j < n; j++ {
|
||||
v, ok := quick.Value(ty, rand)
|
||||
if !ok {
|
||||
t.Errorf("failed to create value")
|
||||
break
|
||||
}
|
||||
|
||||
m1 := v.Elem().Interface()
|
||||
m2 := iface
|
||||
|
||||
marshaled := Marshal(m1)
|
||||
if err := Unmarshal(marshaled, m2); err != nil {
|
||||
t.Errorf("Unmarshal %#v: %s", m1, err)
|
||||
break
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v.Interface(), m2) {
|
||||
t.Errorf("got: %#v\nwant:%#v\n%x", m2, m1, marshaled)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalEmptyPacket(t *testing.T) {
|
||||
var b []byte
|
||||
var m channelRequestSuccessMsg
|
||||
if err := Unmarshal(b, &m); err == nil {
|
||||
t.Fatalf("unmarshal of empty slice succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalUnexpectedPacket(t *testing.T) {
|
||||
type S struct {
|
||||
I uint32 `sshtype:"43"`
|
||||
S string
|
||||
B bool
|
||||
}
|
||||
|
||||
s := S{11, "hello", true}
|
||||
packet := Marshal(s)
|
||||
packet[0] = 42
|
||||
roundtrip := S{}
|
||||
err := Unmarshal(packet, &roundtrip)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, not nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalPtr(t *testing.T) {
|
||||
s := struct {
|
||||
S string
|
||||
}{"hello"}
|
||||
|
||||
m1 := Marshal(s)
|
||||
m2 := Marshal(&s)
|
||||
if !bytes.Equal(m1, m2) {
|
||||
t.Errorf("got %q, want %q for marshaled pointer", m2, m1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBareMarshalUnmarshal(t *testing.T) {
|
||||
type S struct {
|
||||
I uint32
|
||||
S string
|
||||
B bool
|
||||
}
|
||||
|
||||
s := S{42, "hello", true}
|
||||
packet := Marshal(s)
|
||||
roundtrip := S{}
|
||||
Unmarshal(packet, &roundtrip)
|
||||
|
||||
if !reflect.DeepEqual(s, roundtrip) {
|
||||
t.Errorf("got %#v, want %#v", roundtrip, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBareMarshal(t *testing.T) {
|
||||
type S2 struct {
|
||||
I uint32
|
||||
}
|
||||
s := S2{42}
|
||||
packet := Marshal(s)
|
||||
i, rest, ok := parseUint32(packet)
|
||||
if len(rest) > 0 || !ok {
|
||||
t.Errorf("parseInt(%q): parse error", packet)
|
||||
}
|
||||
if i != s.I {
|
||||
t.Errorf("got %d, want %d", i, s.I)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalShortKexInitPacket(t *testing.T) {
|
||||
// This used to panic.
|
||||
// Issue 11348
|
||||
packet := []byte{0x14, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xff, 0xff, 0xff}
|
||||
kim := &kexInitMsg{}
|
||||
if err := Unmarshal(packet, kim); err == nil {
|
||||
t.Error("truncated packet unmarshaled without error")
|
||||
}
|
||||
}
|
||||
|
||||
func randomBytes(out []byte, rand *rand.Rand) {
|
||||
for i := 0; i < len(out); i++ {
|
||||
out[i] = byte(rand.Int31())
|
||||
}
|
||||
}
|
||||
|
||||
func randomNameList(rand *rand.Rand) []string {
|
||||
ret := make([]string, rand.Int31()&15)
|
||||
for i := range ret {
|
||||
s := make([]byte, 1+(rand.Int31()&15))
|
||||
for j := range s {
|
||||
s[j] = 'a' + uint8(rand.Int31()&15)
|
||||
}
|
||||
ret[i] = string(s)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func randomInt(rand *rand.Rand) *big.Int {
|
||||
return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
|
||||
}
|
||||
|
||||
func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
ki := &kexInitMsg{}
|
||||
randomBytes(ki.Cookie[:], rand)
|
||||
ki.KexAlgos = randomNameList(rand)
|
||||
ki.ServerHostKeyAlgos = randomNameList(rand)
|
||||
ki.CiphersClientServer = randomNameList(rand)
|
||||
ki.CiphersServerClient = randomNameList(rand)
|
||||
ki.MACsClientServer = randomNameList(rand)
|
||||
ki.MACsServerClient = randomNameList(rand)
|
||||
ki.CompressionClientServer = randomNameList(rand)
|
||||
ki.CompressionServerClient = randomNameList(rand)
|
||||
ki.LanguagesClientServer = randomNameList(rand)
|
||||
ki.LanguagesServerClient = randomNameList(rand)
|
||||
if rand.Int31()&1 == 1 {
|
||||
ki.FirstKexFollows = true
|
||||
}
|
||||
return reflect.ValueOf(ki)
|
||||
}
|
||||
|
||||
func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
dhi := &kexDHInitMsg{}
|
||||
dhi.X = randomInt(rand)
|
||||
return reflect.ValueOf(dhi)
|
||||
}
|
||||
|
||||
var (
|
||||
_kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
|
||||
_kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
|
||||
|
||||
_kexInit = Marshal(_kexInitMsg)
|
||||
_kexDHInit = Marshal(_kexDHInitMsg)
|
||||
)
|
||||
|
||||
func BenchmarkMarshalKexInitMsg(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Marshal(_kexInitMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalKexInitMsg(b *testing.B) {
|
||||
m := new(kexInitMsg)
|
||||
for i := 0; i < b.N; i++ {
|
||||
Unmarshal(_kexInit, m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalKexDHInitMsg(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Marshal(_kexDHInitMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) {
|
||||
m := new(kexDHInitMsg)
|
||||
for i := 0; i < b.N; i++ {
|
||||
Unmarshal(_kexDHInit, m)
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// debugMux, if set, causes messages in the connection protocol to be
|
||||
// logged.
|
||||
const debugMux = false
|
||||
|
||||
// chanList is a thread safe channel list.
|
||||
type chanList struct {
|
||||
// protects concurrent access to chans
|
||||
sync.Mutex
|
||||
|
||||
// chans are indexed by the local id of the channel, which the
|
||||
// other side should send in the PeersId field.
|
||||
chans []*channel
|
||||
|
||||
// This is a debugging aid: it offsets all IDs by this
|
||||
// amount. This helps distinguish otherwise identical
|
||||
// server/client muxes
|
||||
offset uint32
|
||||
}
|
||||
|
||||
// Assigns a channel ID to the given channel.
|
||||
func (c *chanList) add(ch *channel) uint32 {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for i := range c.chans {
|
||||
if c.chans[i] == nil {
|
||||
c.chans[i] = ch
|
||||
return uint32(i) + c.offset
|
||||
}
|
||||
}
|
||||
c.chans = append(c.chans, ch)
|
||||
return uint32(len(c.chans)-1) + c.offset
|
||||
}
|
||||
|
||||
// getChan returns the channel for the given ID.
|
||||
func (c *chanList) getChan(id uint32) *channel {
|
||||
id -= c.offset
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if id < uint32(len(c.chans)) {
|
||||
return c.chans[id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chanList) remove(id uint32) {
|
||||
id -= c.offset
|
||||
c.Lock()
|
||||
if id < uint32(len(c.chans)) {
|
||||
c.chans[id] = nil
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// dropAll forgets all channels it knows, returning them in a slice.
|
||||
func (c *chanList) dropAll() []*channel {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
var r []*channel
|
||||
|
||||
for _, ch := range c.chans {
|
||||
if ch == nil {
|
||||
continue
|
||||
}
|
||||
r = append(r, ch)
|
||||
}
|
||||
c.chans = nil
|
||||
return r
|
||||
}
|
||||
|
||||
// mux represents the state for the SSH connection protocol, which
|
||||
// multiplexes many channels onto a single packet transport.
|
||||
type mux struct {
|
||||
conn packetConn
|
||||
chanList chanList
|
||||
|
||||
incomingChannels chan NewChannel
|
||||
|
||||
globalSentMu sync.Mutex
|
||||
globalResponses chan interface{}
|
||||
incomingRequests chan *Request
|
||||
|
||||
errCond *sync.Cond
|
||||
err error
|
||||
}
|
||||
|
||||
// When debugging, each new chanList instantiation has a different
|
||||
// offset.
|
||||
var globalOff uint32
|
||||
|
||||
func (m *mux) Wait() error {
|
||||
m.errCond.L.Lock()
|
||||
defer m.errCond.L.Unlock()
|
||||
for m.err == nil {
|
||||
m.errCond.Wait()
|
||||
}
|
||||
return m.err
|
||||
}
|
||||
|
||||
// newMux returns a mux that runs over the given connection.
|
||||
func newMux(p packetConn) *mux {
|
||||
m := &mux{
|
||||
conn: p,
|
||||
incomingChannels: make(chan NewChannel, 16),
|
||||
globalResponses: make(chan interface{}, 1),
|
||||
incomingRequests: make(chan *Request, 16),
|
||||
errCond: newCond(),
|
||||
}
|
||||
if debugMux {
|
||||
m.chanList.offset = atomic.AddUint32(&globalOff, 1)
|
||||
}
|
||||
|
||||
go m.loop()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mux) sendMessage(msg interface{}) error {
|
||||
p := Marshal(msg)
|
||||
return m.conn.writePacket(p)
|
||||
}
|
||||
|
||||
func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) {
|
||||
if wantReply {
|
||||
m.globalSentMu.Lock()
|
||||
defer m.globalSentMu.Unlock()
|
||||
}
|
||||
|
||||
if err := m.sendMessage(globalRequestMsg{
|
||||
Type: name,
|
||||
WantReply: wantReply,
|
||||
Data: payload,
|
||||
}); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if !wantReply {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
msg, ok := <-m.globalResponses
|
||||
if !ok {
|
||||
return false, nil, io.EOF
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *globalRequestFailureMsg:
|
||||
return false, msg.Data, nil
|
||||
case *globalRequestSuccessMsg:
|
||||
return true, msg.Data, nil
|
||||
default:
|
||||
return false, nil, fmt.Errorf("ssh: unexpected response to request: %#v", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// ackRequest must be called after processing a global request that
|
||||
// has WantReply set.
|
||||
func (m *mux) ackRequest(ok bool, data []byte) error {
|
||||
if ok {
|
||||
return m.sendMessage(globalRequestSuccessMsg{Data: data})
|
||||
}
|
||||
return m.sendMessage(globalRequestFailureMsg{Data: data})
|
||||
}
|
||||
|
||||
// TODO(hanwen): Disconnect is a transport layer message. We should
|
||||
// probably send and receive Disconnect somewhere in the transport
|
||||
// code.
|
||||
|
||||
// Disconnect sends a disconnect message.
|
||||
func (m *mux) Disconnect(reason uint32, message string) error {
|
||||
return m.sendMessage(disconnectMsg{
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *mux) Close() error {
|
||||
return m.conn.Close()
|
||||
}
|
||||
|
||||
// loop runs the connection machine. It will process packets until an
|
||||
// error is encountered. To synchronize on loop exit, use mux.Wait.
|
||||
func (m *mux) loop() {
|
||||
var err error
|
||||
for err == nil {
|
||||
err = m.onePacket()
|
||||
}
|
||||
|
||||
for _, ch := range m.chanList.dropAll() {
|
||||
ch.close()
|
||||
}
|
||||
|
||||
close(m.incomingChannels)
|
||||
close(m.incomingRequests)
|
||||
close(m.globalResponses)
|
||||
|
||||
m.conn.Close()
|
||||
|
||||
m.errCond.L.Lock()
|
||||
m.err = err
|
||||
m.errCond.Broadcast()
|
||||
m.errCond.L.Unlock()
|
||||
|
||||
if debugMux {
|
||||
log.Println("loop exit", err)
|
||||
}
|
||||
}
|
||||
|
||||
// onePacket reads and processes one packet.
|
||||
func (m *mux) onePacket() error {
|
||||
packet, err := m.conn.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if debugMux {
|
||||
if packet[0] == msgChannelData || packet[0] == msgChannelExtendedData {
|
||||
log.Printf("decoding(%d): data packet - %d bytes", m.chanList.offset, len(packet))
|
||||
} else {
|
||||
p, _ := decode(packet)
|
||||
log.Printf("decoding(%d): %d %#v - %d bytes", m.chanList.offset, packet[0], p, len(packet))
|
||||
}
|
||||
}
|
||||
|
||||
switch packet[0] {
|
||||
case msgNewKeys:
|
||||
// Ignore notification of key change.
|
||||
return nil
|
||||
case msgDisconnect:
|
||||
return m.handleDisconnect(packet)
|
||||
case msgChannelOpen:
|
||||
return m.handleChannelOpen(packet)
|
||||
case msgGlobalRequest, msgRequestSuccess, msgRequestFailure:
|
||||
return m.handleGlobalPacket(packet)
|
||||
}
|
||||
|
||||
// assume a channel packet.
|
||||
if len(packet) < 5 {
|
||||
return parseError(packet[0])
|
||||
}
|
||||
id := binary.BigEndian.Uint32(packet[1:])
|
||||
ch := m.chanList.getChan(id)
|
||||
if ch == nil {
|
||||
return fmt.Errorf("ssh: invalid channel %d", id)
|
||||
}
|
||||
|
||||
return ch.handlePacket(packet)
|
||||
}
|
||||
|
||||
func (m *mux) handleDisconnect(packet []byte) error {
|
||||
var d disconnectMsg
|
||||
if err := Unmarshal(packet, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if debugMux {
|
||||
log.Printf("caught disconnect: %v", d)
|
||||
}
|
||||
return &d
|
||||
}
|
||||
|
||||
func (m *mux) handleGlobalPacket(packet []byte) error {
|
||||
msg, err := decode(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *globalRequestMsg:
|
||||
m.incomingRequests <- &Request{
|
||||
Type: msg.Type,
|
||||
WantReply: msg.WantReply,
|
||||
Payload: msg.Data,
|
||||
mux: m,
|
||||
}
|
||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||
m.globalResponses <- msg
|
||||
default:
|
||||
panic(fmt.Sprintf("not a global message %#v", msg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleChannelOpen schedules a channel to be Accept()ed.
|
||||
func (m *mux) handleChannelOpen(packet []byte) error {
|
||||
var msg channelOpenMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
failMsg := channelOpenFailureMsg{
|
||||
PeersId: msg.PeersId,
|
||||
Reason: ConnectionFailed,
|
||||
Message: "invalid request",
|
||||
Language: "en_US.UTF-8",
|
||||
}
|
||||
return m.sendMessage(failMsg)
|
||||
}
|
||||
|
||||
c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData)
|
||||
c.remoteId = msg.PeersId
|
||||
c.maxRemotePayload = msg.MaxPacketSize
|
||||
c.remoteWin.add(msg.PeersWindow)
|
||||
m.incomingChannels <- c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Request, error) {
|
||||
ch, err := m.openChannel(chanType, extra)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ch, ch.incomingRequests, nil
|
||||
}
|
||||
|
||||
func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
|
||||
ch := m.newChannel(chanType, channelOutbound, extra)
|
||||
|
||||
ch.maxIncomingPayload = channelMaxPacket
|
||||
|
||||
open := channelOpenMsg{
|
||||
ChanType: chanType,
|
||||
PeersWindow: ch.myWindow,
|
||||
MaxPacketSize: ch.maxIncomingPayload,
|
||||
TypeSpecificData: extra,
|
||||
PeersId: ch.localId,
|
||||
}
|
||||
if err := m.sendMessage(open); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := (<-ch.msg).(type) {
|
||||
case *channelOpenConfirmMsg:
|
||||
return ch, nil
|
||||
case *channelOpenFailureMsg:
|
||||
return nil, &OpenChannelError{msg.Reason, msg.Message}
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unexpected packet in response to channel open: %T", msg)
|
||||
}
|
||||
}
|
||||
@@ -1,525 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func muxPair() (*mux, *mux) {
|
||||
a, b := memPipe()
|
||||
|
||||
s := newMux(a)
|
||||
c := newMux(b)
|
||||
|
||||
return s, c
|
||||
}
|
||||
|
||||
// Returns both ends of a channel, and the mux for the the 2nd
|
||||
// channel.
|
||||
func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
||||
c, s := muxPair()
|
||||
|
||||
res := make(chan *channel, 1)
|
||||
go func() {
|
||||
newCh, ok := <-s.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("No incoming channel")
|
||||
}
|
||||
if newCh.ChannelType() != "chan" {
|
||||
t.Fatalf("got type %q want chan", newCh.ChannelType())
|
||||
}
|
||||
ch, _, err := newCh.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Accept %v", err)
|
||||
}
|
||||
res <- ch.(*channel)
|
||||
}()
|
||||
|
||||
ch, err := c.openChannel("chan", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel: %v", err)
|
||||
}
|
||||
|
||||
return <-res, ch, c
|
||||
}
|
||||
|
||||
// Test that stderr and stdout can be addressed from different
|
||||
// goroutines. This is intended for use with the race detector.
|
||||
func TestMuxChannelExtendedThreadSafety(t *testing.T) {
|
||||
writer, reader, mux := channelPair(t)
|
||||
defer writer.Close()
|
||||
defer reader.Close()
|
||||
defer mux.Close()
|
||||
|
||||
var wr, rd sync.WaitGroup
|
||||
magic := "hello world"
|
||||
|
||||
wr.Add(2)
|
||||
go func() {
|
||||
io.WriteString(writer, magic)
|
||||
wr.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.WriteString(writer.Stderr(), magic)
|
||||
wr.Done()
|
||||
}()
|
||||
|
||||
rd.Add(2)
|
||||
go func() {
|
||||
c, err := ioutil.ReadAll(reader)
|
||||
if string(c) != magic {
|
||||
t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err)
|
||||
}
|
||||
rd.Done()
|
||||
}()
|
||||
go func() {
|
||||
c, err := ioutil.ReadAll(reader.Stderr())
|
||||
if string(c) != magic {
|
||||
t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err)
|
||||
}
|
||||
rd.Done()
|
||||
}()
|
||||
|
||||
wr.Wait()
|
||||
writer.CloseWrite()
|
||||
rd.Wait()
|
||||
}
|
||||
|
||||
func TestMuxReadWrite(t *testing.T) {
|
||||
s, c, mux := channelPair(t)
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
defer mux.Close()
|
||||
|
||||
magic := "hello world"
|
||||
magicExt := "hello stderr"
|
||||
go func() {
|
||||
_, err := s.Write([]byte(magic))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
_, err = s.Extended(1).Write([]byte(magicExt))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
err = s.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
var buf [1024]byte
|
||||
n, err := c.Read(buf[:])
|
||||
if err != nil {
|
||||
t.Fatalf("server Read: %v", err)
|
||||
}
|
||||
got := string(buf[:n])
|
||||
if got != magic {
|
||||
t.Fatalf("server: got %q want %q", got, magic)
|
||||
}
|
||||
|
||||
n, err = c.Extended(1).Read(buf[:])
|
||||
if err != nil {
|
||||
t.Fatalf("server Read: %v", err)
|
||||
}
|
||||
|
||||
got = string(buf[:n])
|
||||
if got != magicExt {
|
||||
t.Fatalf("server: got %q want %q", got, magic)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelOverflow(t *testing.T) {
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
wDone := make(chan int, 1)
|
||||
go func() {
|
||||
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
||||
t.Errorf("could not fill window: %v", err)
|
||||
}
|
||||
writer.Write(make([]byte, 1))
|
||||
wDone <- 1
|
||||
}()
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
|
||||
// Send 1 byte.
|
||||
packet := make([]byte, 1+4+4+1)
|
||||
packet[0] = msgChannelData
|
||||
marshalUint32(packet[1:], writer.remoteId)
|
||||
marshalUint32(packet[5:], uint32(1))
|
||||
packet[9] = 42
|
||||
|
||||
if err := writer.mux.conn.writePacket(packet); err != nil {
|
||||
t.Errorf("could not send packet")
|
||||
}
|
||||
if _, err := reader.SendRequest("hello", true, nil); err == nil {
|
||||
t.Errorf("SendRequest succeeded.")
|
||||
}
|
||||
<-wDone
|
||||
}
|
||||
|
||||
func TestMuxChannelCloseWriteUnblock(t *testing.T) {
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
wDone := make(chan int, 1)
|
||||
go func() {
|
||||
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
||||
t.Errorf("could not fill window: %v", err)
|
||||
}
|
||||
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
|
||||
t.Errorf("got %v, want EOF for unblock write", err)
|
||||
}
|
||||
wDone <- 1
|
||||
}()
|
||||
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
reader.Close()
|
||||
<-wDone
|
||||
}
|
||||
|
||||
func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
wDone := make(chan int, 1)
|
||||
go func() {
|
||||
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
||||
t.Errorf("could not fill window: %v", err)
|
||||
}
|
||||
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
|
||||
t.Errorf("got %v, want EOF for unblock write", err)
|
||||
}
|
||||
wDone <- 1
|
||||
}()
|
||||
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
mux.Close()
|
||||
<-wDone
|
||||
}
|
||||
|
||||
func TestMuxReject(t *testing.T) {
|
||||
client, server := muxPair()
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
|
||||
go func() {
|
||||
ch, ok := <-server.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("Accept")
|
||||
}
|
||||
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
|
||||
t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
||||
}
|
||||
ch.Reject(RejectionReason(42), "message")
|
||||
}()
|
||||
|
||||
ch, err := client.openChannel("ch", []byte("extra"))
|
||||
if ch != nil {
|
||||
t.Fatal("openChannel not rejected")
|
||||
}
|
||||
|
||||
ocf, ok := err.(*OpenChannelError)
|
||||
if !ok {
|
||||
t.Errorf("got %#v want *OpenChannelError", err)
|
||||
} else if ocf.Reason != 42 || ocf.Message != "message" {
|
||||
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
|
||||
}
|
||||
|
||||
want := "ssh: rejected: unknown reason 42 (message)"
|
||||
if err.Error() != want {
|
||||
t.Errorf("got %q, want %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelRequest(t *testing.T) {
|
||||
client, server, mux := channelPair(t)
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
defer mux.Close()
|
||||
|
||||
var received int
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for r := range server.incomingRequests {
|
||||
received++
|
||||
r.Reply(r.Type == "yes", nil)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
_, err := client.SendRequest("yes", false, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
ok, err := client.SendRequest("yes", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Errorf("SendRequest(yes): %v", ok)
|
||||
|
||||
}
|
||||
|
||||
ok, err = client.SendRequest("no", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("SendRequest(no): %v", ok)
|
||||
|
||||
}
|
||||
|
||||
client.Close()
|
||||
wg.Wait()
|
||||
|
||||
if received != 3 {
|
||||
t.Errorf("got %d requests, want %d", received, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxGlobalRequest(t *testing.T) {
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
var seen bool
|
||||
go func() {
|
||||
for r := range serverMux.incomingRequests {
|
||||
seen = seen || r.Type == "peek"
|
||||
if r.WantReply {
|
||||
err := r.Reply(r.Type == "yes",
|
||||
append([]byte(r.Type), r.Payload...))
|
||||
if err != nil {
|
||||
t.Errorf("AckRequest: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
_, _, err := clientMux.SendRequest("peek", false, nil)
|
||||
if err != nil {
|
||||
t.Errorf("SendRequest: %v", err)
|
||||
}
|
||||
|
||||
ok, data, err := clientMux.SendRequest("yes", true, []byte("a"))
|
||||
if !ok || string(data) != "yesa" || err != nil {
|
||||
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
|
||||
ok, data, err)
|
||||
}
|
||||
if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil {
|
||||
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
|
||||
ok, data, err)
|
||||
}
|
||||
|
||||
if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil {
|
||||
t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
|
||||
ok, data, err)
|
||||
}
|
||||
|
||||
clientMux.Disconnect(0, "")
|
||||
if !seen {
|
||||
t.Errorf("never saw 'peek' request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxGlobalRequestUnblock(t *testing.T) {
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, _, err := clientMux.SendRequest("hello", true, nil)
|
||||
result <- err
|
||||
}()
|
||||
|
||||
<-serverMux.incomingRequests
|
||||
serverMux.conn.Close()
|
||||
err := <-result
|
||||
|
||||
if err != io.EOF {
|
||||
t.Errorf("want EOF, got %v", io.EOF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelRequestUnblock(t *testing.T) {
|
||||
a, b, connB := channelPair(t)
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
defer connB.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := a.SendRequest("hello", true, nil)
|
||||
result <- err
|
||||
}()
|
||||
|
||||
<-b.incomingRequests
|
||||
connB.conn.Close()
|
||||
err := <-result
|
||||
|
||||
if err != io.EOF {
|
||||
t.Errorf("want EOF, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxDisconnect(t *testing.T) {
|
||||
a, b := muxPair()
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
go func() {
|
||||
for r := range b.incomingRequests {
|
||||
r.Reply(true, nil)
|
||||
}
|
||||
}()
|
||||
|
||||
a.Disconnect(42, "whatever")
|
||||
ok, _, err := a.SendRequest("hello", true, nil)
|
||||
if ok || err == nil {
|
||||
t.Errorf("got reply after disconnecting")
|
||||
}
|
||||
err = b.Wait()
|
||||
if d, ok := err.(*disconnectMsg); !ok || d.Reason != 42 {
|
||||
t.Errorf("got %#v, want disconnectMsg{Reason:42}", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxCloseChannel(t *testing.T) {
|
||||
r, w, mux := channelPair(t)
|
||||
defer mux.Close()
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
var b [1024]byte
|
||||
_, err := r.Read(b[:])
|
||||
result <- err
|
||||
}()
|
||||
if err := w.Close(); err != nil {
|
||||
t.Errorf("w.Close: %v", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte("hello")); err != io.EOF {
|
||||
t.Errorf("got err %v, want io.EOF after Close", err)
|
||||
}
|
||||
|
||||
if err := <-result; err != io.EOF {
|
||||
t.Errorf("got %v (%T), want io.EOF", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxCloseWriteChannel(t *testing.T) {
|
||||
r, w, mux := channelPair(t)
|
||||
defer mux.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
var b [1024]byte
|
||||
_, err := r.Read(b[:])
|
||||
result <- err
|
||||
}()
|
||||
if err := w.CloseWrite(); err != nil {
|
||||
t.Errorf("w.CloseWrite: %v", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte("hello")); err != io.EOF {
|
||||
t.Errorf("got err %v, want io.EOF after CloseWrite", err)
|
||||
}
|
||||
|
||||
if err := <-result; err != io.EOF {
|
||||
t.Errorf("got %v (%T), want io.EOF", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxInvalidRecord(t *testing.T) {
|
||||
a, b := muxPair()
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
packet := make([]byte, 1+4+4+1)
|
||||
packet[0] = msgChannelData
|
||||
marshalUint32(packet[1:], 29348723 /* invalid channel id */)
|
||||
marshalUint32(packet[5:], 1)
|
||||
packet[9] = 42
|
||||
|
||||
a.conn.writePacket(packet)
|
||||
go a.SendRequest("hello", false, nil)
|
||||
// 'a' wrote an invalid packet, so 'b' has exited.
|
||||
req, ok := <-b.incomingRequests
|
||||
if ok {
|
||||
t.Errorf("got request %#v after receiving invalid packet", req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroWindowAdjust(t *testing.T) {
|
||||
a, b, mux := channelPair(t)
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
defer mux.Close()
|
||||
|
||||
go func() {
|
||||
io.WriteString(a, "hello")
|
||||
// bogus adjust.
|
||||
a.sendMessage(windowAdjustMsg{})
|
||||
io.WriteString(a, "world")
|
||||
a.Close()
|
||||
}()
|
||||
|
||||
want := "helloworld"
|
||||
c, _ := ioutil.ReadAll(b)
|
||||
if string(c) != want {
|
||||
t.Errorf("got %q want %q", c, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxMaxPacketSize(t *testing.T) {
|
||||
a, b, mux := channelPair(t)
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
defer mux.Close()
|
||||
|
||||
large := make([]byte, a.maxRemotePayload+1)
|
||||
packet := make([]byte, 1+4+4+1+len(large))
|
||||
packet[0] = msgChannelData
|
||||
marshalUint32(packet[1:], a.remoteId)
|
||||
marshalUint32(packet[5:], uint32(len(large)))
|
||||
packet[9] = 42
|
||||
|
||||
if err := a.mux.conn.writePacket(packet); err != nil {
|
||||
t.Errorf("could not send packet")
|
||||
}
|
||||
|
||||
go a.SendRequest("hello", false, nil)
|
||||
|
||||
_, ok := <-b.incomingRequests
|
||||
if ok {
|
||||
t.Errorf("connection still alive after receiving large packet.")
|
||||
}
|
||||
}
|
||||
|
||||
// Don't ship code with debug=true.
|
||||
func TestDebug(t *testing.T) {
|
||||
if debugMux {
|
||||
t.Error("mux debug switched on")
|
||||
}
|
||||
if debugHandshake {
|
||||
t.Error("handshake debug switched on")
|
||||
}
|
||||
}
|
||||
@@ -1,493 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// The Permissions type holds fine-grained permissions that are
|
||||
// specific to a user or a specific authentication method for a
|
||||
// user. Permissions, except for "source-address", must be enforced in
|
||||
// the server application layer, after successful authentication. The
|
||||
// Permissions are passed on in ServerConn so a server implementation
|
||||
// can honor them.
|
||||
type Permissions struct {
|
||||
// Critical options restrict default permissions. Common
|
||||
// restrictions are "source-address" and "force-command". If
|
||||
// the server cannot enforce the restriction, or does not
|
||||
// recognize it, the user should not authenticate.
|
||||
CriticalOptions map[string]string
|
||||
|
||||
// Extensions are extra functionality that the server may
|
||||
// offer on authenticated connections. Common extensions are
|
||||
// "permit-agent-forwarding", "permit-X11-forwarding". Lack of
|
||||
// support for an extension does not preclude authenticating a
|
||||
// user.
|
||||
Extensions map[string]string
|
||||
}
|
||||
|
||||
// ServerConfig holds server specific configuration data.
|
||||
type ServerConfig struct {
|
||||
// Config contains configuration shared between client and server.
|
||||
Config
|
||||
|
||||
hostKeys []Signer
|
||||
|
||||
// NoClientAuth is true if clients are allowed to connect without
|
||||
// authenticating.
|
||||
NoClientAuth bool
|
||||
|
||||
// PasswordCallback, if non-nil, is called when a user
|
||||
// attempts to authenticate using a password.
|
||||
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
|
||||
|
||||
// PublicKeyCallback, if non-nil, is called when a client attempts public
|
||||
// key authentication. It must return true if the given public key is
|
||||
// valid for the given user. For example, see CertChecker.Authenticate.
|
||||
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// KeyboardInteractiveCallback, if non-nil, is called when
|
||||
// keyboard-interactive authentication is selected (RFC
|
||||
// 4256). The client object's Challenge function should be
|
||||
// used to query the user. The callback may offer multiple
|
||||
// Challenge rounds. To avoid information leaks, the client
|
||||
// should be presented a challenge even if the user is
|
||||
// unknown.
|
||||
KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error)
|
||||
|
||||
// AuthLogCallback, if non-nil, is called to log all authentication
|
||||
// attempts.
|
||||
AuthLogCallback func(conn ConnMetadata, method string, err error)
|
||||
|
||||
// ServerVersion is the version identification string to
|
||||
// announce in the public handshake.
|
||||
// If empty, a reasonable default is used.
|
||||
ServerVersion string
|
||||
}
|
||||
|
||||
// AddHostKey adds a private key as a host key. If an existing host
|
||||
// key exists with the same algorithm, it is overwritten. Each server
|
||||
// config must have at least one host key.
|
||||
func (s *ServerConfig) AddHostKey(key Signer) {
|
||||
for i, k := range s.hostKeys {
|
||||
if k.PublicKey().Type() == key.PublicKey().Type() {
|
||||
s.hostKeys[i] = key
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.hostKeys = append(s.hostKeys, key)
|
||||
}
|
||||
|
||||
// cachedPubKey contains the results of querying whether a public key is
|
||||
// acceptable for a user.
|
||||
type cachedPubKey struct {
|
||||
user string
|
||||
pubKeyData []byte
|
||||
result error
|
||||
perms *Permissions
|
||||
}
|
||||
|
||||
const maxCachedPubKeys = 16
|
||||
|
||||
// pubKeyCache caches tests for public keys. Since SSH clients
|
||||
// will query whether a public key is acceptable before attempting to
|
||||
// authenticate with it, we end up with duplicate queries for public
|
||||
// key validity. The cache only applies to a single ServerConn.
|
||||
type pubKeyCache struct {
|
||||
keys []cachedPubKey
|
||||
}
|
||||
|
||||
// get returns the result for a given user/algo/key tuple.
|
||||
func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
|
||||
for _, k := range c.keys {
|
||||
if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) {
|
||||
return k, true
|
||||
}
|
||||
}
|
||||
return cachedPubKey{}, false
|
||||
}
|
||||
|
||||
// add adds the given tuple to the cache.
|
||||
func (c *pubKeyCache) add(candidate cachedPubKey) {
|
||||
if len(c.keys) < maxCachedPubKeys {
|
||||
c.keys = append(c.keys, candidate)
|
||||
}
|
||||
}
|
||||
|
||||
// ServerConn is an authenticated SSH connection, as seen from the
|
||||
// server
|
||||
type ServerConn struct {
|
||||
Conn
|
||||
|
||||
// If the succeeding authentication callback returned a
|
||||
// non-nil Permissions pointer, it is stored here.
|
||||
Permissions *Permissions
|
||||
}
|
||||
|
||||
// NewServerConn starts a new SSH server with c as the underlying
|
||||
// transport. It starts with a handshake and, if the handshake is
|
||||
// unsuccessful, it closes the connection and returns an error. The
|
||||
// Request and NewChannel channels must be serviced, or the connection
|
||||
// will hang.
|
||||
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
s := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
perms, err := s.serverHandshake(&fullConf)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil
|
||||
}
|
||||
|
||||
// signAndMarshal signs the data with the appropriate algorithm,
|
||||
// and serializes the result in SSH wire format.
|
||||
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
|
||||
sig, err := k.Sign(rand, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Marshal(sig), nil
|
||||
}
|
||||
|
||||
// handshake performs key exchange and user authentication.
|
||||
func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) {
|
||||
if len(config.hostKeys) == 0 {
|
||||
return nil, errors.New("ssh: server has no host keys")
|
||||
}
|
||||
|
||||
if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && config.KeyboardInteractiveCallback == nil {
|
||||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||
}
|
||||
|
||||
if config.ServerVersion != "" {
|
||||
s.serverVersion = []byte(config.ServerVersion)
|
||||
} else {
|
||||
s.serverVersion = []byte(packageVersion)
|
||||
}
|
||||
var err error
|
||||
s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
|
||||
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
|
||||
|
||||
if err := s.transport.requestKeyChange(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if packet, err := s.transport.readPacket(); err != nil {
|
||||
return nil, err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return nil, unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
|
||||
// We just did the key change, so the session ID is established.
|
||||
s.sessionID = s.transport.getSessionID()
|
||||
|
||||
var packet []byte
|
||||
if packet, err = s.transport.readPacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serviceRequest serviceRequestMsg
|
||||
if err = Unmarshal(packet, &serviceRequest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if serviceRequest.Service != serviceUserAuth {
|
||||
return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
|
||||
}
|
||||
serviceAccept := serviceAcceptMsg{
|
||||
Service: serviceUserAuth,
|
||||
}
|
||||
if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
perms, err := s.serverAuthenticate(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.mux = newMux(s.transport)
|
||||
return perms, err
|
||||
}
|
||||
|
||||
func isAcceptableAlgo(algo string) bool {
|
||||
switch algo {
|
||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSourceAddress(addr net.Addr, sourceAddr string) error {
|
||||
if addr == nil {
|
||||
return errors.New("ssh: no address known for client, but source-address match required")
|
||||
}
|
||||
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
|
||||
}
|
||||
|
||||
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
|
||||
if bytes.Equal(allowedIP, tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
_, ipNet, err := net.ParseCIDR(sourceAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
|
||||
}
|
||||
|
||||
if ipNet.Contains(tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
|
||||
}
|
||||
|
||||
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
||||
var err error
|
||||
var cache pubKeyCache
|
||||
var perms *Permissions
|
||||
|
||||
userAuthLoop:
|
||||
for {
|
||||
var userAuthReq userAuthRequestMsg
|
||||
if packet, err := s.transport.readPacket(); err != nil {
|
||||
return nil, err
|
||||
} else if err = Unmarshal(packet, &userAuthReq); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userAuthReq.Service != serviceSSH {
|
||||
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
|
||||
}
|
||||
|
||||
s.user = userAuthReq.User
|
||||
perms = nil
|
||||
authErr := errors.New("no auth passed yet")
|
||||
|
||||
switch userAuthReq.Method {
|
||||
case "none":
|
||||
if config.NoClientAuth {
|
||||
s.user = ""
|
||||
authErr = nil
|
||||
}
|
||||
case "password":
|
||||
if config.PasswordCallback == nil {
|
||||
authErr = errors.New("ssh: password auth not configured")
|
||||
break
|
||||
}
|
||||
payload := userAuthReq.Payload
|
||||
if len(payload) < 1 || payload[0] != 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
payload = payload[1:]
|
||||
password, payload, ok := parseString(payload)
|
||||
if !ok || len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
perms, authErr = config.PasswordCallback(s, password)
|
||||
case "keyboard-interactive":
|
||||
if config.KeyboardInteractiveCallback == nil {
|
||||
authErr = errors.New("ssh: keyboard-interactive auth not configubred")
|
||||
break
|
||||
}
|
||||
|
||||
prompter := &sshClientKeyboardInteractive{s}
|
||||
perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
|
||||
case "publickey":
|
||||
if config.PublicKeyCallback == nil {
|
||||
authErr = errors.New("ssh: publickey auth not configured")
|
||||
break
|
||||
}
|
||||
payload := userAuthReq.Payload
|
||||
if len(payload) < 1 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
isQuery := payload[0] == 0
|
||||
payload = payload[1:]
|
||||
algoBytes, payload, ok := parseString(payload)
|
||||
if !ok {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
algo := string(algoBytes)
|
||||
if !isAcceptableAlgo(algo) {
|
||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
|
||||
break
|
||||
}
|
||||
|
||||
pubKeyData, payload, ok := parseString(payload)
|
||||
if !ok {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
pubKey, err := ParsePublicKey(pubKeyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
candidate, ok := cache.get(s.user, pubKeyData)
|
||||
if !ok {
|
||||
candidate.user = s.user
|
||||
candidate.pubKeyData = pubKeyData
|
||||
candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
|
||||
if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
||||
candidate.result = checkSourceAddress(
|
||||
s.RemoteAddr(),
|
||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption])
|
||||
}
|
||||
cache.add(candidate)
|
||||
}
|
||||
|
||||
if isQuery {
|
||||
// The client can query if the given public key
|
||||
// would be okay.
|
||||
if len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
if candidate.result == nil {
|
||||
okMsg := userAuthPubKeyOkMsg{
|
||||
Algo: algo,
|
||||
PubKey: pubKeyData,
|
||||
}
|
||||
if err = s.transport.writePacket(Marshal(&okMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue userAuthLoop
|
||||
}
|
||||
authErr = candidate.result
|
||||
} else {
|
||||
sig, payload, ok := parseSignature(payload)
|
||||
if !ok || len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
// Ensure the public key algo and signature algo
|
||||
// are supported. Compare the private key
|
||||
// algorithm name that corresponds to algo with
|
||||
// sig.Format. This is usually the same, but
|
||||
// for certs, the names differ.
|
||||
if !isAcceptableAlgo(sig.Format) {
|
||||
break
|
||||
}
|
||||
signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData)
|
||||
|
||||
if err := pubKey.Verify(signedData, sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authErr = candidate.result
|
||||
perms = candidate.perms
|
||||
}
|
||||
default:
|
||||
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
|
||||
}
|
||||
|
||||
if config.AuthLogCallback != nil {
|
||||
config.AuthLogCallback(s, userAuthReq.Method, authErr)
|
||||
}
|
||||
|
||||
if authErr == nil {
|
||||
break userAuthLoop
|
||||
}
|
||||
|
||||
var failureMsg userAuthFailureMsg
|
||||
if config.PasswordCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
||||
}
|
||||
if config.PublicKeyCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "publickey")
|
||||
}
|
||||
if config.KeyboardInteractiveCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
||||
}
|
||||
|
||||
if len(failureMsg.Methods) == 0 {
|
||||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||
}
|
||||
|
||||
if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
|
||||
// asking the client on the other side of a ServerConn.
|
||||
type sshClientKeyboardInteractive struct {
|
||||
*connection
|
||||
}
|
||||
|
||||
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
||||
if len(questions) != len(echos) {
|
||||
return nil, errors.New("ssh: echos and questions must have equal length")
|
||||
}
|
||||
|
||||
var prompts []byte
|
||||
for i := range questions {
|
||||
prompts = appendString(prompts, questions[i])
|
||||
prompts = appendBool(prompts, echos[i])
|
||||
}
|
||||
|
||||
if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{
|
||||
Instruction: instruction,
|
||||
NumPrompts: uint32(len(questions)),
|
||||
Prompts: prompts,
|
||||
})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if packet[0] != msgUserAuthInfoResponse {
|
||||
return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0])
|
||||
}
|
||||
packet = packet[1:]
|
||||
|
||||
n, packet, ok := parseUint32(packet)
|
||||
if !ok || int(n) != len(questions) {
|
||||
return nil, parseError(msgUserAuthInfoResponse)
|
||||
}
|
||||
|
||||
for i := uint32(0); i < n; i++ {
|
||||
ans, rest, ok := parseString(packet)
|
||||
if !ok {
|
||||
return nil, parseError(msgUserAuthInfoResponse)
|
||||
}
|
||||
|
||||
answers = append(answers, string(ans))
|
||||
packet = rest
|
||||
}
|
||||
if len(packet) != 0 {
|
||||
return nil, errors.New("ssh: junk at end of message")
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
@@ -1,605 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Session implements an interactive session described in
|
||||
// "RFC 4254, section 6".
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Signal string
|
||||
|
||||
// POSIX signals as listed in RFC 4254 Section 6.10.
|
||||
const (
|
||||
SIGABRT Signal = "ABRT"
|
||||
SIGALRM Signal = "ALRM"
|
||||
SIGFPE Signal = "FPE"
|
||||
SIGHUP Signal = "HUP"
|
||||
SIGILL Signal = "ILL"
|
||||
SIGINT Signal = "INT"
|
||||
SIGKILL Signal = "KILL"
|
||||
SIGPIPE Signal = "PIPE"
|
||||
SIGQUIT Signal = "QUIT"
|
||||
SIGSEGV Signal = "SEGV"
|
||||
SIGTERM Signal = "TERM"
|
||||
SIGUSR1 Signal = "USR1"
|
||||
SIGUSR2 Signal = "USR2"
|
||||
)
|
||||
|
||||
var signals = map[Signal]int{
|
||||
SIGABRT: 6,
|
||||
SIGALRM: 14,
|
||||
SIGFPE: 8,
|
||||
SIGHUP: 1,
|
||||
SIGILL: 4,
|
||||
SIGINT: 2,
|
||||
SIGKILL: 9,
|
||||
SIGPIPE: 13,
|
||||
SIGQUIT: 3,
|
||||
SIGSEGV: 11,
|
||||
SIGTERM: 15,
|
||||
}
|
||||
|
||||
type TerminalModes map[uint8]uint32
|
||||
|
||||
// POSIX terminal mode flags as listed in RFC 4254 Section 8.
|
||||
const (
|
||||
tty_OP_END = 0
|
||||
VINTR = 1
|
||||
VQUIT = 2
|
||||
VERASE = 3
|
||||
VKILL = 4
|
||||
VEOF = 5
|
||||
VEOL = 6
|
||||
VEOL2 = 7
|
||||
VSTART = 8
|
||||
VSTOP = 9
|
||||
VSUSP = 10
|
||||
VDSUSP = 11
|
||||
VREPRINT = 12
|
||||
VWERASE = 13
|
||||
VLNEXT = 14
|
||||
VFLUSH = 15
|
||||
VSWTCH = 16
|
||||
VSTATUS = 17
|
||||
VDISCARD = 18
|
||||
IGNPAR = 30
|
||||
PARMRK = 31
|
||||
INPCK = 32
|
||||
ISTRIP = 33
|
||||
INLCR = 34
|
||||
IGNCR = 35
|
||||
ICRNL = 36
|
||||
IUCLC = 37
|
||||
IXON = 38
|
||||
IXANY = 39
|
||||
IXOFF = 40
|
||||
IMAXBEL = 41
|
||||
ISIG = 50
|
||||
ICANON = 51
|
||||
XCASE = 52
|
||||
ECHO = 53
|
||||
ECHOE = 54
|
||||
ECHOK = 55
|
||||
ECHONL = 56
|
||||
NOFLSH = 57
|
||||
TOSTOP = 58
|
||||
IEXTEN = 59
|
||||
ECHOCTL = 60
|
||||
ECHOKE = 61
|
||||
PENDIN = 62
|
||||
OPOST = 70
|
||||
OLCUC = 71
|
||||
ONLCR = 72
|
||||
OCRNL = 73
|
||||
ONOCR = 74
|
||||
ONLRET = 75
|
||||
CS7 = 90
|
||||
CS8 = 91
|
||||
PARENB = 92
|
||||
PARODD = 93
|
||||
TTY_OP_ISPEED = 128
|
||||
TTY_OP_OSPEED = 129
|
||||
)
|
||||
|
||||
// A Session represents a connection to a remote command or shell.
|
||||
type Session struct {
|
||||
// Stdin specifies the remote process's standard input.
|
||||
// If Stdin is nil, the remote process reads from an empty
|
||||
// bytes.Buffer.
|
||||
Stdin io.Reader
|
||||
|
||||
// Stdout and Stderr specify the remote process's standard
|
||||
// output and error.
|
||||
//
|
||||
// If either is nil, Run connects the corresponding file
|
||||
// descriptor to an instance of ioutil.Discard. There is a
|
||||
// fixed amount of buffering that is shared for the two streams.
|
||||
// If either blocks it may eventually cause the remote
|
||||
// command to block.
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
ch Channel // the channel backing this session
|
||||
started bool // true once Start, Run or Shell is invoked.
|
||||
copyFuncs []func() error
|
||||
errors chan error // one send per copyFunc
|
||||
|
||||
// true if pipe method is active
|
||||
stdinpipe, stdoutpipe, stderrpipe bool
|
||||
|
||||
// stdinPipeWriter is non-nil if StdinPipe has not been called
|
||||
// and Stdin was specified by the user; it is the write end of
|
||||
// a pipe connecting Session.Stdin to the stdin channel.
|
||||
stdinPipeWriter io.WriteCloser
|
||||
|
||||
exitStatus chan error
|
||||
}
|
||||
|
||||
// SendRequest sends an out-of-band channel request on the SSH channel
|
||||
// underlying the session.
|
||||
func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
|
||||
return s.ch.SendRequest(name, wantReply, payload)
|
||||
}
|
||||
|
||||
func (s *Session) Close() error {
|
||||
return s.ch.Close()
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.4.
|
||||
type setenvRequest struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Setenv sets an environment variable that will be applied to any
|
||||
// command executed by Shell or Run.
|
||||
func (s *Session) Setenv(name, value string) error {
|
||||
msg := setenvRequest{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: setenv failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.2.
|
||||
type ptyRequestMsg struct {
|
||||
Term string
|
||||
Columns uint32
|
||||
Rows uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
Modelist string
|
||||
}
|
||||
|
||||
// RequestPty requests the association of a pty with the session on the remote host.
|
||||
func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
|
||||
var tm []byte
|
||||
for k, v := range termmodes {
|
||||
kv := struct {
|
||||
Key byte
|
||||
Val uint32
|
||||
}{k, v}
|
||||
|
||||
tm = append(tm, Marshal(&kv)...)
|
||||
}
|
||||
tm = append(tm, tty_OP_END)
|
||||
req := ptyRequestMsg{
|
||||
Term: term,
|
||||
Columns: uint32(w),
|
||||
Rows: uint32(h),
|
||||
Width: uint32(w * 8),
|
||||
Height: uint32(h * 8),
|
||||
Modelist: string(tm),
|
||||
}
|
||||
ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: pty-req failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.5.
|
||||
type subsystemRequestMsg struct {
|
||||
Subsystem string
|
||||
}
|
||||
|
||||
// RequestSubsystem requests the association of a subsystem with the session on the remote host.
|
||||
// A subsystem is a predefined command that runs in the background when the ssh session is initiated
|
||||
func (s *Session) RequestSubsystem(subsystem string) error {
|
||||
msg := subsystemRequestMsg{
|
||||
Subsystem: subsystem,
|
||||
}
|
||||
ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: subsystem request failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.9.
|
||||
type signalMsg struct {
|
||||
Signal string
|
||||
}
|
||||
|
||||
// Signal sends the given signal to the remote process.
|
||||
// sig is one of the SIG* constants.
|
||||
func (s *Session) Signal(sig Signal) error {
|
||||
msg := signalMsg{
|
||||
Signal: string(sig),
|
||||
}
|
||||
|
||||
_, err := s.ch.SendRequest("signal", false, Marshal(&msg))
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.5.
|
||||
type execMsg struct {
|
||||
Command string
|
||||
}
|
||||
|
||||
// Start runs cmd on the remote host. Typically, the remote
|
||||
// server passes cmd to the shell for interpretation.
|
||||
// A Session only accepts one call to Run, Start or Shell.
|
||||
func (s *Session) Start(cmd string) error {
|
||||
if s.started {
|
||||
return errors.New("ssh: session already started")
|
||||
}
|
||||
req := execMsg{
|
||||
Command: cmd,
|
||||
}
|
||||
|
||||
ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
|
||||
if err == nil && !ok {
|
||||
err = fmt.Errorf("ssh: command %v failed", cmd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.start()
|
||||
}
|
||||
|
||||
// Run runs cmd on the remote host. Typically, the remote
|
||||
// server passes cmd to the shell for interpretation.
|
||||
// A Session only accepts one call to Run, Start, Shell, Output,
|
||||
// or CombinedOutput.
|
||||
//
|
||||
// The returned error is nil if the command runs, has no problems
|
||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
||||
// status.
|
||||
//
|
||||
// If the command fails to run or doesn't complete successfully, the
|
||||
// error is of type *ExitError. Other error types may be
|
||||
// returned for I/O problems.
|
||||
func (s *Session) Run(cmd string) error {
|
||||
err := s.Start(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Wait()
|
||||
}
|
||||
|
||||
// Output runs cmd on the remote host and returns its standard output.
|
||||
func (s *Session) Output(cmd string) ([]byte, error) {
|
||||
if s.Stdout != nil {
|
||||
return nil, errors.New("ssh: Stdout already set")
|
||||
}
|
||||
var b bytes.Buffer
|
||||
s.Stdout = &b
|
||||
err := s.Run(cmd)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
type singleWriter struct {
|
||||
b bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *singleWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Write(p)
|
||||
}
|
||||
|
||||
// CombinedOutput runs cmd on the remote host and returns its combined
|
||||
// standard output and standard error.
|
||||
func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
|
||||
if s.Stdout != nil {
|
||||
return nil, errors.New("ssh: Stdout already set")
|
||||
}
|
||||
if s.Stderr != nil {
|
||||
return nil, errors.New("ssh: Stderr already set")
|
||||
}
|
||||
var b singleWriter
|
||||
s.Stdout = &b
|
||||
s.Stderr = &b
|
||||
err := s.Run(cmd)
|
||||
return b.b.Bytes(), err
|
||||
}
|
||||
|
||||
// Shell starts a login shell on the remote host. A Session only
|
||||
// accepts one call to Run, Start, Shell, Output, or CombinedOutput.
|
||||
func (s *Session) Shell() error {
|
||||
if s.started {
|
||||
return errors.New("ssh: session already started")
|
||||
}
|
||||
|
||||
ok, err := s.ch.SendRequest("shell", true, nil)
|
||||
if err == nil && !ok {
|
||||
return fmt.Errorf("ssh: cound not start shell")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.start()
|
||||
}
|
||||
|
||||
func (s *Session) start() error {
|
||||
s.started = true
|
||||
|
||||
type F func(*Session)
|
||||
for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
|
||||
setupFd(s)
|
||||
}
|
||||
|
||||
s.errors = make(chan error, len(s.copyFuncs))
|
||||
for _, fn := range s.copyFuncs {
|
||||
go func(fn func() error) {
|
||||
s.errors <- fn()
|
||||
}(fn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the remote command to exit.
|
||||
//
|
||||
// The returned error is nil if the command runs, has no problems
|
||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
||||
// status.
|
||||
//
|
||||
// If the command fails to run or doesn't complete successfully, the
|
||||
// error is of type *ExitError. Other error types may be
|
||||
// returned for I/O problems.
|
||||
func (s *Session) Wait() error {
|
||||
if !s.started {
|
||||
return errors.New("ssh: session not started")
|
||||
}
|
||||
waitErr := <-s.exitStatus
|
||||
|
||||
if s.stdinPipeWriter != nil {
|
||||
s.stdinPipeWriter.Close()
|
||||
}
|
||||
var copyError error
|
||||
for _ = range s.copyFuncs {
|
||||
if err := <-s.errors; err != nil && copyError == nil {
|
||||
copyError = err
|
||||
}
|
||||
}
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
return copyError
|
||||
}
|
||||
|
||||
func (s *Session) wait(reqs <-chan *Request) error {
|
||||
wm := Waitmsg{status: -1}
|
||||
// Wait for msg channel to be closed before returning.
|
||||
for msg := range reqs {
|
||||
switch msg.Type {
|
||||
case "exit-status":
|
||||
d := msg.Payload
|
||||
wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
|
||||
case "exit-signal":
|
||||
var sigval struct {
|
||||
Signal string
|
||||
CoreDumped bool
|
||||
Error string
|
||||
Lang string
|
||||
}
|
||||
if err := Unmarshal(msg.Payload, &sigval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Must sanitize strings?
|
||||
wm.signal = sigval.Signal
|
||||
wm.msg = sigval.Error
|
||||
wm.lang = sigval.Lang
|
||||
default:
|
||||
// This handles keepalives and matches
|
||||
// OpenSSH's behaviour.
|
||||
if msg.WantReply {
|
||||
msg.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
if wm.status == 0 {
|
||||
return nil
|
||||
}
|
||||
if wm.status == -1 {
|
||||
// exit-status was never sent from server
|
||||
if wm.signal == "" {
|
||||
return errors.New("wait: remote command exited without exit status or exit signal")
|
||||
}
|
||||
wm.status = 128
|
||||
if _, ok := signals[Signal(wm.signal)]; ok {
|
||||
wm.status += signals[Signal(wm.signal)]
|
||||
}
|
||||
}
|
||||
return &ExitError{wm}
|
||||
}
|
||||
|
||||
func (s *Session) stdin() {
|
||||
if s.stdinpipe {
|
||||
return
|
||||
}
|
||||
var stdin io.Reader
|
||||
if s.Stdin == nil {
|
||||
stdin = new(bytes.Buffer)
|
||||
} else {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
_, err := io.Copy(w, s.Stdin)
|
||||
w.CloseWithError(err)
|
||||
}()
|
||||
stdin, s.stdinPipeWriter = r, w
|
||||
}
|
||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
||||
_, err := io.Copy(s.ch, stdin)
|
||||
if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) stdout() {
|
||||
if s.stdoutpipe {
|
||||
return
|
||||
}
|
||||
if s.Stdout == nil {
|
||||
s.Stdout = ioutil.Discard
|
||||
}
|
||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
||||
_, err := io.Copy(s.Stdout, s.ch)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) stderr() {
|
||||
if s.stderrpipe {
|
||||
return
|
||||
}
|
||||
if s.Stderr == nil {
|
||||
s.Stderr = ioutil.Discard
|
||||
}
|
||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
||||
_, err := io.Copy(s.Stderr, s.ch.Stderr())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// sessionStdin reroutes Close to CloseWrite.
|
||||
type sessionStdin struct {
|
||||
io.Writer
|
||||
ch Channel
|
||||
}
|
||||
|
||||
func (s *sessionStdin) Close() error {
|
||||
return s.ch.CloseWrite()
|
||||
}
|
||||
|
||||
// StdinPipe returns a pipe that will be connected to the
|
||||
// remote command's standard input when the command starts.
|
||||
func (s *Session) StdinPipe() (io.WriteCloser, error) {
|
||||
if s.Stdin != nil {
|
||||
return nil, errors.New("ssh: Stdin already set")
|
||||
}
|
||||
if s.started {
|
||||
return nil, errors.New("ssh: StdinPipe after process started")
|
||||
}
|
||||
s.stdinpipe = true
|
||||
return &sessionStdin{s.ch, s.ch}, nil
|
||||
}
|
||||
|
||||
// StdoutPipe returns a pipe that will be connected to the
|
||||
// remote command's standard output when the command starts.
|
||||
// There is a fixed amount of buffering that is shared between
|
||||
// stdout and stderr streams. If the StdoutPipe reader is
|
||||
// not serviced fast enough it may eventually cause the
|
||||
// remote command to block.
|
||||
func (s *Session) StdoutPipe() (io.Reader, error) {
|
||||
if s.Stdout != nil {
|
||||
return nil, errors.New("ssh: Stdout already set")
|
||||
}
|
||||
if s.started {
|
||||
return nil, errors.New("ssh: StdoutPipe after process started")
|
||||
}
|
||||
s.stdoutpipe = true
|
||||
return s.ch, nil
|
||||
}
|
||||
|
||||
// StderrPipe returns a pipe that will be connected to the
|
||||
// remote command's standard error when the command starts.
|
||||
// There is a fixed amount of buffering that is shared between
|
||||
// stdout and stderr streams. If the StderrPipe reader is
|
||||
// not serviced fast enough it may eventually cause the
|
||||
// remote command to block.
|
||||
func (s *Session) StderrPipe() (io.Reader, error) {
|
||||
if s.Stderr != nil {
|
||||
return nil, errors.New("ssh: Stderr already set")
|
||||
}
|
||||
if s.started {
|
||||
return nil, errors.New("ssh: StderrPipe after process started")
|
||||
}
|
||||
s.stderrpipe = true
|
||||
return s.ch.Stderr(), nil
|
||||
}
|
||||
|
||||
// newSession returns a new interactive session on the remote host.
|
||||
func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
|
||||
s := &Session{
|
||||
ch: ch,
|
||||
}
|
||||
s.exitStatus = make(chan error, 1)
|
||||
go func() {
|
||||
s.exitStatus <- s.wait(reqs)
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// An ExitError reports unsuccessful completion of a remote command.
|
||||
type ExitError struct {
|
||||
Waitmsg
|
||||
}
|
||||
|
||||
func (e *ExitError) Error() string {
|
||||
return e.Waitmsg.String()
|
||||
}
|
||||
|
||||
// Waitmsg stores the information about an exited remote command
|
||||
// as reported by Wait.
|
||||
type Waitmsg struct {
|
||||
status int
|
||||
signal string
|
||||
msg string
|
||||
lang string
|
||||
}
|
||||
|
||||
// ExitStatus returns the exit status of the remote command.
|
||||
func (w Waitmsg) ExitStatus() int {
|
||||
return w.status
|
||||
}
|
||||
|
||||
// Signal returns the exit signal of the remote command if
|
||||
// it was terminated violently.
|
||||
func (w Waitmsg) Signal() string {
|
||||
return w.signal
|
||||
}
|
||||
|
||||
// Msg returns the exit message given by the remote command
|
||||
func (w Waitmsg) Msg() string {
|
||||
return w.msg
|
||||
}
|
||||
|
||||
// Lang returns the language tag. See RFC 3066
|
||||
func (w Waitmsg) Lang() string {
|
||||
return w.lang
|
||||
}
|
||||
|
||||
func (w Waitmsg) String() string {
|
||||
return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
|
||||
}
|
||||
Loaded 100 of 321 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user