├── .gitignore ├── README.md ├── conf ├── app.sql ├── app_conf.ini ├── haproxy_conf_common.json └── md5passwd ├── restart_haproxy.sh ├── screenshot1.png ├── screenshot2.png ├── src ├── applicationDB │ └── applicationDB.go ├── code.google.com │ └── p │ │ └── go.crypto │ │ └── ssh │ │ ├── agent.go │ │ ├── buffer.go │ │ ├── buffer_test.go │ │ ├── certs.go │ │ ├── channel.go │ │ ├── cipher.go │ │ ├── cipher_test.go │ │ ├── client.go │ │ ├── client_auth.go │ │ ├── client_auth_test.go │ │ ├── client_test.go │ │ ├── common.go │ │ ├── common_test.go │ │ ├── doc.go │ │ ├── example_test.go │ │ ├── kex_test.go │ │ ├── keys.go │ │ ├── mac.go │ │ ├── messages.go │ │ ├── messages_test.go │ │ ├── server.go │ │ ├── server_terminal.go │ │ ├── session.go │ │ ├── session_test.go │ │ ├── tcpip.go │ │ ├── tcpip_test.go │ │ ├── terminal │ │ ├── terminal.go │ │ ├── terminal_test.go │ │ ├── util.go │ │ ├── util_bsd.go │ │ └── util_linux.go │ │ ├── test │ │ ├── doc.go │ │ ├── forward_unix_test.go │ │ ├── keys_test.go │ │ ├── session_test.go │ │ ├── tcpip_test.go │ │ └── test_unix_test.go │ │ ├── transport.go │ │ └── transport_test.go ├── config │ └── config.go ├── github.com │ └── abbot │ │ └── go-http-auth │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── auth.go │ │ ├── basic.go │ │ ├── basic_test.go │ │ ├── digest.go │ │ ├── digest_test.go │ │ ├── examples │ │ ├── basic.go │ │ ├── digest.go │ │ └── wrapped.go │ │ ├── md5crypt.go │ │ ├── md5crypt_test.go │ │ ├── misc.go │ │ ├── misc_test.go │ │ ├── test.htdigest │ │ ├── test.htpasswd │ │ ├── users.go │ │ └── users_test.go ├── haproxyconsole │ └── main.go ├── sshoperation │ └── sshoperation.go └── tools │ └── tools.go ├── static ├── alertify │ ├── src │ │ └── alertify.js │ └── themes │ │ ├── alertify.bootstrap.css │ │ ├── alertify.core.css │ │ └── alertify.default.css ├── bootstrap │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ └── bootstrap.min.css │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js ├── jquery-1.7.2.js ├── main.js └── style.css └── template ├── footer.tmpl ├── header.tmpl ├── index.tmpl ├── listenlist.tmpl └── statspage.tmpl /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | log/ 4 | pkg/ 5 | out/ 6 | bin/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HAProxyConsole是一个简单的HAProxy负载均衡任务管理系统。由于HAProxy的负载均衡任务可能会很多,手动编辑配置文件非常不方便、不安全,所以实现一个友好的管理系统是非常必要的。 2 | 3 | ### 功能点: 4 | 5 | 1. TCP协议负载均衡任务的增删改、任务的列表展示; 6 | 2. 一键应用最新配置到主服务器或从服务器并重新HAProxy进程; 7 | 3. 修改一个配置项即可在JSON文件存储和数据库存储之间切换; 8 | 4. 内置小工具用于不同存储方式之间的数据转换; 9 | 5. 内嵌主从HAProxy自带数据统计页面,方便查看信息; 10 | 6. 分/不分业务端口自动分配、指定分配端口模式; 11 | 7. 内置配置文件正确性检查功能;等... 12 | 13 | *基于Go语言标准库http实现自带Web server,一般情况不需再使用nginx/apache。* 14 | 15 | ### 使用场景(系统结构图) 16 | 17 | ![High Availability Load Balancer](https://raw.github.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/high-availability-load-balancer.png) 18 | 19 | ### 基本功能页面截图 20 | 21 | ![screenshot-1](https://raw.github.com/youngsterxyf/haproxyconsole/master/screenshot1.png) 22 | 23 | ![screenshot-2](https://raw.github.com/youngsterxyf/haproxyconsole/master/screenshot2.png) 24 | 25 | ### 配置: 26 | 27 | conf目录下有4个文件: 28 | 29 | - app.sql:如果选择以MySQL来存储,则执行该文件中的sql语句创建数据表 30 | - app_conf.ini:该文件为haproxyconsole的主配置文件,使用之前阅读每个配置项的说明信息,按照说明修改配置。 31 | - DB.json:该文件是在选择以JSON文件存储时,自动生成的存储文件。 32 | - haproxy_conf_common.json:该json文件包含4项数据-“Global”、“Defaults”、“ListenStats”、“ListenCommon”,“Global”和“Default”对应HAProxy配置的Global和Defaults部分,“ListenStats”是启用HAProxy自带的数据统计页面,用户可能需要修改该功能启用的端口,“ListenCommon”是所有HAProxyConsole管理的TCP负载均衡任务在生成HAProxy配置listen块时的通用部分。 33 | 34 | ### 编译: 35 | 36 | cd src && go build main.go 37 | 38 | ### 使用: 39 | 40 | 启动HAProxyConsole(假设HAProxy部署在`/usr/local`目录下):`cd /usr/local/haproxyconsole/bin && ./haproxyconsole &`,默认端口为9090,可使用选项`-p`来自定义端口,如:`cd /usr/local/haproxyconsole/bin && ./haproxyconsole -p 8080`(注意该端口不应在HAProxyConsole为HAProxy负载均衡任务自动分配的端口范围之内(10000-20000))。 41 | 42 | 若需转换数据存储方式,则可通过内置工具来完成:`cd /usr/local/haproxyconsole/bin && ./haproxyconsole -t`,该工具完成的操作是:若StoreScheme设定为0,则从DBDriverName和DBDataSourceName 指定数据库的haproxymapinfo数据表中读取数据,转换成json格式存入FileToReplaceDB指定路径的JSON文件中。 43 | 44 | ### 缺点: 45 | 1. 没有手动编辑HAProxy配置文件灵活,没法对某些负载均衡任务做详细的定制。 46 | 2. 目前只实现了3层(TCP协议)负载均衡任务管理。 47 | 48 | -------------------------------------------------------------------------------- /conf/app.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE haproxyconsole; 2 | USE haproxyconsole; 3 | DROP TABLE IF EXISTS `haproxymapinfo`; 4 | CREATE TABLE `haproxymapinfo` ( 5 | `id` int(11) NOT NULL AUTO_INCREMENT, 6 | `servers` varchar(1024) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 7 | `vport` int(10) NOT NULL, 8 | `comment` varchar(1024) DEFAULT '', 9 | `logornot` int(1) DEFAULT '1', 10 | `datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 11 | PRIMARY KEY (`id`) 12 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /conf/app_conf.ini: -------------------------------------------------------------------------------- 1 | ; The configuration file consists of sections, 2 | ; led by a "*[section]*" header and followed by "*name: value*" entries 3 | ; "*name=value*" is also accepted. Note that leading whitespace is removed from values. 4 | ; The optional values can contain format strings which refer to other values in the same section, 5 | ; or values in a special DEFAULT section. Additional defaults can be provided on initialization 6 | ; and retrieval. Comments are indicated by ";" or "#"; a comment may begin anywhere on a line, 7 | ; including on the same line after parameters or section declarations. 8 | 9 | [mode] 10 | ; 如果BusinessList的值留空,则为单业务模式,该业务的可自动分配的虚拟端口范围为10000-19999 11 | ; 如果需要多业务模式,则按照格式“业务名称,可分配的开始端口-可分配的结束端口;业务名称,可分配的开始端口-可分配的结束端口;...”来填写业务列表。 12 | BusinessList= 13 | 14 | [master] 15 | ; 主HAProxy配置文件的路径 16 | MasterConf=/usr/local/haproxy/conf/haproxy.conf 17 | ; 主HAProxy重启脚本的路径 18 | MasterRestartScript=/usr/local/haproxy/restart_haproxy.sh 19 | 20 | 21 | [slave] 22 | ; 1为密码访问,2为已打通信任关系,无需密码 23 | CopyMethod=1 24 | ; 从HAProxy机器远程登录:服务器ip,port,用户名,密码 25 | SlaveServerIp=xxx.xxx.xxx.xxx 26 | SlaveServerSSHPort=xxx 27 | SlaveRemoteUser=root 28 | SlaveRemotePasswd=xxx 29 | ; 30 | SSHCommandPath=/usr/bin/ssh 31 | ; 32 | ScpCommandPath=/usr/bin/scp 33 | 34 | ; 从HAProxy配置文件的路径 35 | SlaveConf=/usr/local/haproxy/conf/haproxy.conf 36 | ; 从HAProxy重启脚本的路径 37 | SlaveRestartScript=/usr/local/haproxy/restart_haproxy.sh 38 | 39 | 40 | [store] 41 | ; 数据存储方式:数据库(DB,以0表示)与文件(FILE,以1表示)两种 42 | StoreScheme=1 43 | 44 | ; MySQL数据库连接信息 45 | DBDriverName=mysql 46 | DBDataSourceName=xxx:xxx@tcp(xxx.xxx.xxx.xxx:3306)/haproxyconsole?charset=utf8 47 | 48 | ; 若采用文件来存储负载均衡任务数据,则需指定该文件路径 49 | ; 负载均衡任务数据文件路径 50 | FileToReplaceDB=../conf/DB.json 51 | 52 | 53 | [stats] 54 | ; 主HAProxy数据统计页面URL 55 | MasterStatsPage=http://xxx.xxx.xxx.xxx:xxx 56 | 57 | ; 从HAProxy数据统计页面URL 58 | SlaveStatsPage=http://xxx.xxx.xxx.xxx:xxx 59 | 60 | [others] 61 | ; 主从HAProxy的VIP 62 | Vip=xxx.xxx.xxx.xxx 63 | 64 | ; 根据负载均衡任务数据生成的HAProxy新配置文件存放路径 65 | NewHAProxyConfPath=../conf/haproxy_new.conf 66 | -------------------------------------------------------------------------------- /conf/haproxy_conf_common.json: -------------------------------------------------------------------------------- 1 | { 2 | "Global" : [ 3 | "global", 4 | "log 127.0.0.1 local3 debug", 5 | "daemon", 6 | "user root", 7 | "group root", 8 | "maxconn 65535", 9 | "nbproc 1", 10 | "pidfile /usr/local/haproxy/haproxy.pid" 11 | ], 12 | "Defaults":[ 13 | "defaults", 14 | "option redispatch", 15 | "option dontlognull", 16 | "retries 3", 17 | "balance roundrobin", 18 | "timeout connect 5000ms", 19 | "timeout client 1800000ms", 20 | "timeout server 1800000ms" 21 | ], 22 | "ListenStats":[ 23 | "listen stats", 24 | "bind *:8888", 25 | "mode http", 26 | "option httplog", 27 | "log global", 28 | "stats uri /", 29 | "stats realm Haproxy \\ statistic", 30 | "stats refresh 30s", 31 | "stats enable", 32 | "stats show-legends", 33 | "stats admin if TRUE", 34 | "stats auth admin:admin", 35 | "stats hide-version" 36 | ], 37 | "ListenCommon":[ 38 | "mode tcp" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /conf/md5passwd: -------------------------------------------------------------------------------- 1 | admin:$apr1$Gelo3ulT$mvgBL41Vc5eCGba6WfOfe/ -------------------------------------------------------------------------------- /restart_haproxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/conf/haproxy.conf -st `cat /usr/local/haproxy/haproxy.pid` 4 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitelife/haproxyconsole/943b7910b9faaaea3733db468dd16405c4933071/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitelife/haproxyconsole/943b7910b9faaaea3733db468dd16405c4933071/screenshot2.png -------------------------------------------------------------------------------- /src/applicationDB/applicationDB.go: -------------------------------------------------------------------------------- 1 | package applicationDB 2 | 3 | import ( 4 | "bytes" 5 | "config" 6 | "database/sql" 7 | "encoding/json" 8 | "errors" 9 | //"fmt" 10 | "io" 11 | "os" 12 | "sort" 13 | ) 14 | 15 | type NewConfDataType struct { 16 | Servers string 17 | BackupServers string 18 | VPort int 19 | LogOrNot int 20 | } 21 | 22 | type DataRow struct { 23 | Id int 24 | Servers string 25 | BackupServers string 26 | VPort int 27 | Comment string 28 | LogOrNot int 29 | DateTime string 30 | } 31 | 32 | type DB interface { 33 | QueryNewConfData() ([]NewConfDataType, error) 34 | QueryVPort() ([]int, error) 35 | InsertNewTask(string, string, int, string, int, string) error 36 | QueryForTaskList() ([]DataRow, error) 37 | DeleteTask(int) (sql.Result, error) 38 | UpdateTaskInfo(string, string, string, int, string, int) error 39 | Close() error 40 | } 41 | 42 | type database struct { 43 | Db *sql.DB 44 | } 45 | type fileForStore struct { 46 | F *os.File 47 | } 48 | 49 | /* 50 | // 实现数据库查询中的"ORDER BY vport ASC" 51 | func quickSort(values []DataRow, left int, right int) { 52 | temp := values[left] 53 | p := left 54 | i, j := left, right 55 | 56 | for i <= j { 57 | for j >= p && values[j].VPort >= temp.VPort { 58 | j-- 59 | } 60 | if j >= p { 61 | values[p] = values[j] 62 | p = j 63 | } 64 | 65 | if values[i].VPort <= temp.VPort && i <= p { 66 | i++ 67 | } 68 | 69 | if i <= p { 70 | values[p] = values[i] 71 | p = i 72 | } 73 | } 74 | values[p] = temp 75 | if p-left > 1 { 76 | quickSort(values, left, p-1) 77 | } 78 | if right-p > 1 { 79 | quickSort(values, p+1, right) 80 | } 81 | } 82 | */ 83 | type DataRows []DataRow 84 | 85 | func (drs DataRows) Len() int { 86 | return len(drs) 87 | } 88 | 89 | func (drs DataRows) Swap(i, j int) { 90 | drs[i], drs[j] = drs[j], drs[i] 91 | } 92 | 93 | func (drs DataRows) Less(i, j int) bool { 94 | return drs[i].VPort <= drs[j].VPort 95 | } 96 | 97 | // 该辅助函数来自golang标准库io/ioutil/ioutil.go 98 | func readAll(r io.Reader, capacity int64) (b []byte, err error) { 99 | buf := bytes.NewBuffer(make([]byte, 0, capacity)) 100 | 101 | defer func() { 102 | e := recover() 103 | if e == nil { 104 | return 105 | } 106 | if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { 107 | err = panicErr 108 | } else { 109 | panic(e) 110 | } 111 | }() 112 | _, err = buf.ReadFrom(r) 113 | return buf.Bytes(), err 114 | } 115 | 116 | func readJson(f fileForStore) (allData []DataRow, err error) { 117 | f.F.Seek(0, 0) 118 | var n int64 119 | if fi, err := f.F.Stat(); err == nil { 120 | if size := fi.Size(); size < 1e9 { 121 | n = size 122 | } 123 | } 124 | content, err := readAll(f.F, n+bytes.MinRead) 125 | f.F.Seek(0, 0) 126 | err = json.Unmarshal(content, &allData) 127 | if len(allData) > 1 { 128 | //quickSort(allData, 0, len(allData)-1) 129 | sort.Sort(DataRows(allData)) 130 | } 131 | return 132 | } 133 | 134 | // 模拟数据库增删改操作的返回结果Result 135 | /* 136 | type Result interface { 137 | LastInsertId() (int64, error) 138 | RowsAffected() (int64, error) 139 | } 140 | */ 141 | type ResultDeleteFromFile struct { 142 | LastIdInsert int64 143 | AffectedRows int64 144 | } 145 | 146 | func (rdff ResultDeleteFromFile) LastInsertId() (int64, error) { 147 | return rdff.LastIdInsert, nil 148 | } 149 | 150 | func (rdff ResultDeleteFromFile) RowsAffected() (int64, error) { 151 | return rdff.AffectedRows, nil 152 | } 153 | 154 | // SELECT servers, backup_servers, vport, logornot FROM haproxymapinfo ORDER BY vport ASC 155 | // QueryNewConfData() ([]NewConfDataType, error) 156 | func (d database) QueryNewConfData() (dataList []NewConfDataType, err error) { 157 | dataList = make([]NewConfDataType, 0, 100) 158 | rows, err := d.Db.Query("SELECT servers, vport, logornot FROM haproxymapinfo ORDER BY vport ASC") 159 | var servers string 160 | var backupServers string 161 | var vport int 162 | var logOrNot int 163 | for rows.Next() { 164 | err = rows.Scan(&servers, &backupServers, &vport, &logOrNot) 165 | dataList = append(dataList, NewConfDataType{Servers: servers, BackupServers: backupServers, VPort: vport, LogOrNot: logOrNot}) 166 | } 167 | return 168 | } 169 | 170 | func (f fileForStore) QueryNewConfData() (dataList []NewConfDataType, err error) { 171 | allData, err := readJson(f) 172 | dataList = make([]NewConfDataType, 0, 100) 173 | taskNum := len(allData) 174 | for index := 0; index < taskNum; index++ { 175 | task := allData[index] 176 | dataList = append(dataList, NewConfDataType{Servers: task.Servers, BackupServers: task.BackupServers, VPort: task.VPort, LogOrNot: task.LogOrNot}) 177 | } 178 | return 179 | } 180 | 181 | // SELECT vport FROM haproxymapinfo ORDER BY vport ASC 182 | // QueryVPort() ([]int, error) 183 | func (d database) QueryVPort() (vportList []int, err error) { 184 | rows, err := d.Db.Query("SELECT vport FROM haproxymapinfo ORDER BY vport ASC") 185 | vportList = make([]int, 0, 100) 186 | var vport int 187 | for rows.Next() { 188 | err = rows.Scan(&vport) 189 | vportList = append(vportList, vport) 190 | } 191 | return 192 | } 193 | 194 | func (f fileForStore) QueryVPort() (vportList []int, err error) { 195 | allData, err := readJson(f) 196 | vportList = make([]int, 0, 100) 197 | taskNum := len(allData) 198 | for index := 0; index < taskNum; index++ { 199 | vportList = append(vportList, allData[index].VPort) 200 | } 201 | return 202 | } 203 | 204 | // db.Exec("INSERT INTO haproxymapinfo (servers, backup_servers, vport, comment, logornot, datetime) VALUES (?, ?, ?, ?, ?)", servers, backupServers, vportToAssign, comment, logOrNot, now) 205 | // InsertNewTask(string, int, string, int, string) (error) 206 | func (d database) InsertNewTask(servers string, backupServers string, vportToAssign int, comment string, logOrNot int, now string) (err error) { 207 | _, err = d.Db.Exec("INSERT INTO haproxymapinfo (servers, backup_servers, vport, comment, logornot, datetime) VALUES (?, ?, ?, ?, ?)", servers, backupServers, vportToAssign, comment, logOrNot, now) 208 | return 209 | } 210 | 211 | func (f fileForStore) InsertNewTask(servers string, backupServers string, vportToAssign int, comment string, logOrNot int, now string) (err error) { 212 | allData, err := readJson(f) 213 | rowNum := len(allData) 214 | maxId := -1 215 | if rowNum > 0 { 216 | maxId = 0 217 | for index := 0; index < rowNum; index++ { 218 | row := allData[index] 219 | if row.Id > maxId { 220 | maxId = row.Id 221 | } 222 | } 223 | } 224 | //fmt.Printf("maxId: %d", maxId) 225 | oneRowData := DataRow{ 226 | Id: maxId + 1, 227 | Servers: servers, 228 | BackupServers: backupServers, 229 | VPort: vportToAssign, 230 | Comment: comment, 231 | LogOrNot: logOrNot, 232 | DateTime: now, 233 | } 234 | allData = append(allData, oneRowData) 235 | dataJson, err := json.MarshalIndent(allData, "", " ") 236 | f.F.Truncate(0) 237 | f.F.Write(dataJson) 238 | f.F.Sync() 239 | return 240 | } 241 | 242 | // SELECT id, servers, backup_servers, vport, comment, logornot, datetime FROM haproxymapinfo ORDER BY vport ASC 243 | // QueryForTaskList() ([]DataRow, error) 244 | func (d database) QueryForTaskList() (taskList []DataRow, err error) { 245 | rows, err := d.Db.Query("SELECT id, servers, backup_servers, vport, comment, logornot, datetime FROM haproxymapinfo ORDER BY vport ASC") 246 | var id int 247 | var servers string 248 | var backupServers string 249 | var vport int 250 | var comment string 251 | var logornot int 252 | var datetime string 253 | taskList = make([]DataRow, 0, 100) 254 | for rows.Next() { 255 | err = rows.Scan(&id, &servers, &backupServers, &vport, &comment, &logornot, &datetime) 256 | taskList = append(taskList, DataRow{Id: id, Servers: servers, BackupServers: backupServers, VPort: vport, Comment: comment, LogOrNot: logornot, DateTime: datetime}) 257 | } 258 | return 259 | } 260 | 261 | func (f fileForStore) QueryForTaskList() (taskList []DataRow, err error) { 262 | taskList, err = readJson(f) 263 | return 264 | } 265 | 266 | // db.Exec("DELETE FROM haproxymapinfo WHERE id=?", id) 267 | // DeleteTask(int) (Result, error) 268 | func (d database) DeleteTask(id int) (result sql.Result, err error) { 269 | result, err = d.Db.Exec("DELETE FROM haproxymapinfo WHERE id=?", id) 270 | return 271 | } 272 | 273 | func (f fileForStore) DeleteTask(id int) (result sql.Result, err error) { 274 | allData, err := readJson(f) 275 | rowNum := len(allData) 276 | dataAfterDel := make([]DataRow, 0, 100) 277 | for index := 0; index < rowNum; index++ { 278 | row := allData[index] 279 | if row.Id != id { 280 | dataAfterDel = append(dataAfterDel, row) 281 | } 282 | } 283 | dataJson, err := json.MarshalIndent(dataAfterDel, "", " ") 284 | f.F.Truncate(0) 285 | f.F.Write(dataJson) 286 | f.F.Sync() 287 | rdff := ResultDeleteFromFile{LastIdInsert: -1, AffectedRows: 1} 288 | return rdff, nil 289 | } 290 | 291 | // db.Exec("UPDATE haproxymapinfo SET servers=?, backup_servers=?, comment=?, logornot=?, datetime=? WHERE id=?", servers, backupServers, comment, logornot, now, id) 292 | // UpdateTaskInfo(string, string, int, string, int) (error) 293 | func (d database) UpdateTaskInfo(servers string, backupServers string, comment string, logornot int, now string, id int) (err error) { 294 | _, err = d.Db.Exec("UPDATE haproxymapinfo SET servers=?, backup_servers=?, comment=?, logornot=?, datetime=? WHERE id=?", servers, backupServers, comment, logornot, now, id) 295 | return 296 | } 297 | 298 | func (f fileForStore) UpdateTaskInfo(servers string, backupServers string, comment string, logornot int, now string, id int) (err error) { 299 | allData, err := readJson(f) 300 | rowNum := len(allData) 301 | for index := 0; index < rowNum; index++ { 302 | row := allData[index] 303 | if row.Id == id { 304 | dataOneRow := DataRow{ 305 | Id: id, 306 | Servers: servers, 307 | BackupServers: backupServers, 308 | VPort: row.VPort, 309 | Comment: comment, 310 | LogOrNot: logornot, 311 | DateTime: now, 312 | } 313 | allData[index] = dataOneRow 314 | } 315 | } 316 | dataJson, err := json.MarshalIndent(allData, "", " ") 317 | f.F.Truncate(0) 318 | f.F.Write(dataJson) 319 | f.F.Sync() 320 | return 321 | } 322 | 323 | // Close()(error) 324 | func (d database) Close() (err error) { 325 | err = d.Db.Close() 326 | return 327 | } 328 | 329 | func (f fileForStore) Close() (err error) { 330 | err = f.F.Close() 331 | return 332 | } 333 | 334 | func InitStoreConnection(appConf config.ConfigInfo) (db DB, e error) { 335 | if appConf.StoreScheme == 0 { 336 | d, err := sql.Open(appConf.DBDriverName, appConf.DBDataSourceName) 337 | if err != nil { 338 | e = errors.New("数据库连接出错!") 339 | } 340 | db = database{ 341 | Db: d, 342 | } 343 | } 344 | 345 | if appConf.StoreScheme == 1 { 346 | f, err := os.OpenFile(appConf.FileToReplaceDB, os.O_CREATE|os.O_RDWR, 0666) 347 | if err != nil { 348 | e = errors.New("文件打开出错!") 349 | } 350 | db = fileForStore{ 351 | F: f, 352 | } 353 | } 354 | return 355 | } 356 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/agent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "encoding/base64" 9 | "errors" 10 | "io" 11 | "sync" 12 | ) 13 | 14 | // See [PROTOCOL.agent], section 3. 15 | const ( 16 | // 3.2 Requests from client to agent for protocol 2 key operations 17 | agentRequestIdentities = 11 18 | agentSignRequest = 13 19 | agentAddIdentity = 17 20 | agentRemoveIdentity = 18 21 | agentRemoveAllIdentities = 19 22 | agentAddIdConstrained = 25 23 | 24 | // 3.3 Key-type independent requests from client to agent 25 | agentAddSmartcardKey = 20 26 | agentRemoveSmartcardKey = 21 27 | agentLock = 22 28 | agentUnlock = 23 29 | agentAddSmartcardKeyConstrained = 26 30 | 31 | // 3.4 Generic replies from agent to client 32 | agentFailure = 5 33 | agentSuccess = 6 34 | 35 | // 3.6 Replies from agent to client for protocol 2 key operations 36 | agentIdentitiesAnswer = 12 37 | agentSignResponse = 14 38 | 39 | // 3.7 Key constraint identifiers 40 | agentConstrainLifetime = 1 41 | agentConstrainConfirm = 2 42 | ) 43 | 44 | // maxAgentResponseBytes is the maximum agent reply size that is accepted. This 45 | // is a sanity check, not a limit in the spec. 46 | const maxAgentResponseBytes = 16 << 20 47 | 48 | // Agent messages: 49 | // These structures mirror the wire format of the corresponding ssh agent 50 | // messages found in [PROTOCOL.agent]. 51 | 52 | type failureAgentMsg struct{} 53 | 54 | type successAgentMsg struct{} 55 | 56 | // See [PROTOCOL.agent], section 2.5.2. 57 | type requestIdentitiesAgentMsg struct{} 58 | 59 | // See [PROTOCOL.agent], section 2.5.2. 60 | type identitiesAnswerAgentMsg struct { 61 | NumKeys uint32 62 | Keys []byte `ssh:"rest"` 63 | } 64 | 65 | // See [PROTOCOL.agent], section 2.6.2. 66 | type signRequestAgentMsg struct { 67 | KeyBlob []byte 68 | Data []byte 69 | Flags uint32 70 | } 71 | 72 | // See [PROTOCOL.agent], section 2.6.2. 73 | type signResponseAgentMsg struct { 74 | SigBlob []byte 75 | } 76 | 77 | // AgentKey represents a protocol 2 key as defined in [PROTOCOL.agent], 78 | // section 2.5.2. 79 | type AgentKey struct { 80 | blob []byte 81 | Comment string 82 | } 83 | 84 | // String returns the storage form of an agent key with the format, base64 85 | // encoded serialized key, and the comment if it is not empty. 86 | func (ak *AgentKey) String() string { 87 | algo, _, ok := parseString(ak.blob) 88 | if !ok { 89 | return "ssh: malformed key" 90 | } 91 | 92 | s := string(algo) + " " + base64.StdEncoding.EncodeToString(ak.blob) 93 | 94 | if ak.Comment != "" { 95 | s += " " + ak.Comment 96 | } 97 | 98 | return s 99 | } 100 | 101 | // Key returns an agent's public key as one of the supported key or certificate types. 102 | func (ak *AgentKey) Key() (interface{}, error) { 103 | if key, _, ok := parsePubKey(ak.blob); ok { 104 | return key, nil 105 | } 106 | return nil, errors.New("ssh: failed to parse key blob") 107 | } 108 | 109 | func parseAgentKey(in []byte) (out *AgentKey, rest []byte, ok bool) { 110 | ak := new(AgentKey) 111 | 112 | if ak.blob, in, ok = parseString(in); !ok { 113 | return 114 | } 115 | 116 | comment, in, ok := parseString(in) 117 | if !ok { 118 | return 119 | } 120 | ak.Comment = string(comment) 121 | 122 | return ak, in, true 123 | } 124 | 125 | // AgentClient provides a means to communicate with an ssh agent process based 126 | // on the protocol described in [PROTOCOL.agent]?rev=1.6. 127 | type AgentClient struct { 128 | // conn is typically represented by using a *net.UnixConn 129 | conn io.ReadWriter 130 | // mu is used to prevent concurrent access to the agent 131 | mu sync.Mutex 132 | } 133 | 134 | // NewAgentClient creates and returns a new *AgentClient using the 135 | // passed in io.ReadWriter as a connection to a ssh agent. 136 | func NewAgentClient(rw io.ReadWriter) *AgentClient { 137 | return &AgentClient{conn: rw} 138 | } 139 | 140 | // sendAndReceive sends req to the agent and waits for a reply. On success, 141 | // the reply is unmarshaled into reply and replyType is set to the first byte of 142 | // the reply, which contains the type of the message. 143 | func (ac *AgentClient) sendAndReceive(req []byte) (reply interface{}, replyType uint8, err error) { 144 | // ac.mu prevents multiple, concurrent requests. Since the agent is typically 145 | // on the same machine, we don't attempt to pipeline the requests. 146 | ac.mu.Lock() 147 | defer ac.mu.Unlock() 148 | 149 | msg := make([]byte, stringLength(len(req))) 150 | marshalString(msg, req) 151 | if _, err = ac.conn.Write(msg); err != nil { 152 | return 153 | } 154 | 155 | var respSizeBuf [4]byte 156 | if _, err = io.ReadFull(ac.conn, respSizeBuf[:]); err != nil { 157 | return 158 | } 159 | respSize, _, _ := parseUint32(respSizeBuf[:]) 160 | 161 | if respSize > maxAgentResponseBytes { 162 | err = errors.New("ssh: agent reply too large") 163 | return 164 | } 165 | 166 | buf := make([]byte, respSize) 167 | if _, err = io.ReadFull(ac.conn, buf); err != nil { 168 | return 169 | } 170 | return unmarshalAgentMsg(buf) 171 | } 172 | 173 | // RequestIdentities queries the agent for protocol 2 keys as defined in 174 | // [PROTOCOL.agent] section 2.5.2. 175 | func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) { 176 | req := marshal(agentRequestIdentities, requestIdentitiesAgentMsg{}) 177 | 178 | msg, msgType, err := ac.sendAndReceive(req) 179 | if err != nil { 180 | return nil, err 181 | } 182 | 183 | switch msg := msg.(type) { 184 | case *identitiesAnswerAgentMsg: 185 | if msg.NumKeys > maxAgentResponseBytes/8 { 186 | return nil, errors.New("ssh: too many keys in agent reply") 187 | } 188 | keys := make([]*AgentKey, msg.NumKeys) 189 | data := msg.Keys 190 | for i := uint32(0); i < msg.NumKeys; i++ { 191 | var key *AgentKey 192 | var ok bool 193 | if key, data, ok = parseAgentKey(data); !ok { 194 | return nil, ParseError{agentIdentitiesAnswer} 195 | } 196 | keys[i] = key 197 | } 198 | return keys, nil 199 | case *failureAgentMsg: 200 | return nil, errors.New("ssh: failed to list keys") 201 | } 202 | return nil, UnexpectedMessageError{agentIdentitiesAnswer, msgType} 203 | } 204 | 205 | // SignRequest requests the signing of data by the agent using a protocol 2 key 206 | // as defined in [PROTOCOL.agent] section 2.6.2. 207 | func (ac *AgentClient) SignRequest(key interface{}, data []byte) ([]byte, error) { 208 | req := marshal(agentSignRequest, signRequestAgentMsg{ 209 | KeyBlob: serializePublicKey(key), 210 | Data: data, 211 | }) 212 | 213 | msg, msgType, err := ac.sendAndReceive(req) 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | switch msg := msg.(type) { 219 | case *signResponseAgentMsg: 220 | return msg.SigBlob, nil 221 | case *failureAgentMsg: 222 | return nil, errors.New("ssh: failed to sign challenge") 223 | } 224 | return nil, UnexpectedMessageError{agentSignResponse, msgType} 225 | } 226 | 227 | // unmarshalAgentMsg parses an agent message in packet, returning the parsed 228 | // form and the message type of packet. 229 | func unmarshalAgentMsg(packet []byte) (interface{}, uint8, error) { 230 | if len(packet) < 1 { 231 | return nil, 0, ParseError{0} 232 | } 233 | var msg interface{} 234 | switch packet[0] { 235 | case agentFailure: 236 | msg = new(failureAgentMsg) 237 | case agentSuccess: 238 | msg = new(successAgentMsg) 239 | case agentIdentitiesAnswer: 240 | msg = new(identitiesAnswerAgentMsg) 241 | case agentSignResponse: 242 | msg = new(signResponseAgentMsg) 243 | default: 244 | return nil, 0, UnexpectedMessageError{0, packet[0]} 245 | } 246 | if err := unmarshal(msg, packet, packet[0]); err != nil { 247 | return nil, 0, err 248 | } 249 | return msg, packet[0], nil 250 | } 251 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "io" 9 | "sync" 10 | ) 11 | 12 | // buffer provides a linked list buffer for data exchange 13 | // between producer and consumer. Theoretically the buffer is 14 | // of unlimited capacity as it does no allocation of its own. 15 | type buffer struct { 16 | // protects concurrent access to head, tail and closed 17 | *sync.Cond 18 | 19 | head *element // the buffer that will be read first 20 | tail *element // the buffer that will be read last 21 | 22 | closed bool 23 | } 24 | 25 | // An element represents a single link in a linked list. 26 | type element struct { 27 | buf []byte 28 | next *element 29 | } 30 | 31 | // newBuffer returns an empty buffer that is not closed. 32 | func newBuffer() *buffer { 33 | e := new(element) 34 | b := &buffer{ 35 | Cond: newCond(), 36 | head: e, 37 | tail: e, 38 | } 39 | return b 40 | } 41 | 42 | // write makes buf available for Read to receive. 43 | // buf must not be modified after the call to write. 44 | func (b *buffer) write(buf []byte) { 45 | b.Cond.L.Lock() 46 | defer b.Cond.L.Unlock() 47 | e := &element{buf: buf} 48 | b.tail.next = e 49 | b.tail = e 50 | b.Cond.Signal() 51 | } 52 | 53 | // eof closes the buffer. Reads from the buffer once all 54 | // the data has been consumed will receive os.EOF. 55 | func (b *buffer) eof() error { 56 | b.Cond.L.Lock() 57 | defer b.Cond.L.Unlock() 58 | b.closed = true 59 | b.Cond.Signal() 60 | return nil 61 | } 62 | 63 | // Read reads data from the internal buffer in buf. 64 | // Reads will block if no data is available, or until 65 | // the buffer is closed. 66 | func (b *buffer) Read(buf []byte) (n int, err error) { 67 | b.Cond.L.Lock() 68 | defer b.Cond.L.Unlock() 69 | for len(buf) > 0 { 70 | // if there is data in b.head, copy it 71 | if len(b.head.buf) > 0 { 72 | r := copy(buf, b.head.buf) 73 | buf, b.head.buf = buf[r:], b.head.buf[r:] 74 | n += r 75 | continue 76 | } 77 | // if there is a next buffer, make it the head 78 | if len(b.head.buf) == 0 && b.head != b.tail { 79 | b.head = b.head.next 80 | continue 81 | } 82 | // if at least one byte has been copied, return 83 | if n > 0 { 84 | break 85 | } 86 | // if nothing was read, and there is nothing outstanding 87 | // check to see if the buffer is closed. 88 | if b.closed { 89 | err = io.EOF 90 | break 91 | } 92 | // out of buffers, wait for producer 93 | b.Cond.Wait() 94 | } 95 | return 96 | } 97 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/buffer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "io" 9 | "testing" 10 | ) 11 | 12 | var BYTES = []byte("abcdefghijklmnopqrstuvwxyz") 13 | 14 | func TestBufferReadwrite(t *testing.T) { 15 | b := newBuffer() 16 | b.write(BYTES[:10]) 17 | r, _ := b.Read(make([]byte, 10)) 18 | if r != 10 { 19 | t.Fatalf("Expected written == read == 10, written: 10, read %d", r) 20 | } 21 | 22 | b = newBuffer() 23 | b.write(BYTES[:5]) 24 | r, _ = b.Read(make([]byte, 10)) 25 | if r != 5 { 26 | t.Fatalf("Expected written == read == 5, written: 5, read %d", r) 27 | } 28 | 29 | b = newBuffer() 30 | b.write(BYTES[:10]) 31 | r, _ = b.Read(make([]byte, 5)) 32 | if r != 5 { 33 | t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r) 34 | } 35 | 36 | b = newBuffer() 37 | b.write(BYTES[:5]) 38 | b.write(BYTES[5:15]) 39 | r, _ = b.Read(make([]byte, 10)) 40 | r2, _ := b.Read(make([]byte, 10)) 41 | if r != 10 || r2 != 5 || 15 != r+r2 { 42 | t.Fatal("Expected written == read == 15") 43 | } 44 | } 45 | 46 | func TestBufferClose(t *testing.T) { 47 | b := newBuffer() 48 | b.write(BYTES[:10]) 49 | b.eof() 50 | _, err := b.Read(make([]byte, 5)) 51 | if err != nil { 52 | t.Fatal("expected read of 5 to not return EOF") 53 | } 54 | b = newBuffer() 55 | b.write(BYTES[:10]) 56 | b.eof() 57 | r, err := b.Read(make([]byte, 5)) 58 | r2, err2 := b.Read(make([]byte, 10)) 59 | if r != 5 || r2 != 5 || err != nil || err2 != nil { 60 | t.Fatal("expected reads of 5 and 5") 61 | } 62 | 63 | b = newBuffer() 64 | b.write(BYTES[:10]) 65 | b.eof() 66 | r, err = b.Read(make([]byte, 5)) 67 | r2, err2 = b.Read(make([]byte, 10)) 68 | r3, err3 := b.Read(make([]byte, 10)) 69 | if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF { 70 | t.Fatal("expected reads of 5 and 5 and 0, with EOF") 71 | } 72 | 73 | b = newBuffer() 74 | b.write(make([]byte, 5)) 75 | b.write(make([]byte, 10)) 76 | b.eof() 77 | r, err = b.Read(make([]byte, 9)) 78 | r2, err2 = b.Read(make([]byte, 3)) 79 | r3, err3 = b.Read(make([]byte, 3)) 80 | r4, err4 := b.Read(make([]byte, 10)) 81 | if err != nil || err2 != nil || err3 != nil || err4 != io.EOF { 82 | t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4) 83 | } 84 | if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 { 85 | t.Fatal("Expected written == read == 15", r, r2, r3, r4) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/certs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "crypto/dsa" 9 | "crypto/ecdsa" 10 | "crypto/rsa" 11 | "time" 12 | ) 13 | 14 | // These constants from [PROTOCOL.certkeys] represent the algorithm names 15 | // for certificate types supported by this package. 16 | const ( 17 | CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" 18 | CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" 19 | CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" 20 | CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" 21 | CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" 22 | ) 23 | 24 | // Certificate types are used to specify whether a certificate is for identification 25 | // of a user or a host. Current identities are defined in [PROTOCOL.certkeys]. 26 | const ( 27 | UserCert = 1 28 | HostCert = 2 29 | ) 30 | 31 | type signature struct { 32 | Format string 33 | Blob []byte 34 | } 35 | 36 | type tuple struct { 37 | Name string 38 | Data string 39 | } 40 | 41 | // An OpenSSHCertV01 represents an OpenSSH certificate as defined in 42 | // [PROTOCOL.certkeys]?rev=1.8. 43 | type OpenSSHCertV01 struct { 44 | Nonce []byte 45 | Key interface{} // rsa, dsa, or ecdsa *PublicKey 46 | Serial uint64 47 | Type uint32 48 | KeyId string 49 | ValidPrincipals []string 50 | ValidAfter, ValidBefore time.Time 51 | CriticalOptions []tuple 52 | Extensions []tuple 53 | Reserved []byte 54 | SignatureKey interface{} // rsa, dsa, or ecdsa *PublicKey 55 | Signature *signature 56 | } 57 | 58 | func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) { 59 | cert := new(OpenSSHCertV01) 60 | 61 | if cert.Nonce, in, ok = parseString(in); !ok { 62 | return 63 | } 64 | 65 | switch algo { 66 | case CertAlgoRSAv01: 67 | var rsaPubKey *rsa.PublicKey 68 | if rsaPubKey, in, ok = parseRSA(in); !ok { 69 | return 70 | } 71 | cert.Key = rsaPubKey 72 | case CertAlgoDSAv01: 73 | var dsaPubKey *dsa.PublicKey 74 | if dsaPubKey, in, ok = parseDSA(in); !ok { 75 | return 76 | } 77 | cert.Key = dsaPubKey 78 | case CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: 79 | var ecdsaPubKey *ecdsa.PublicKey 80 | if ecdsaPubKey, in, ok = parseECDSA(in); !ok { 81 | return 82 | } 83 | cert.Key = ecdsaPubKey 84 | default: 85 | ok = false 86 | return 87 | } 88 | 89 | if cert.Serial, in, ok = parseUint64(in); !ok { 90 | return 91 | } 92 | 93 | if cert.Type, in, ok = parseUint32(in); !ok || cert.Type != UserCert && cert.Type != HostCert { 94 | return 95 | } 96 | 97 | keyId, in, ok := parseString(in) 98 | if !ok { 99 | return 100 | } 101 | cert.KeyId = string(keyId) 102 | 103 | if cert.ValidPrincipals, in, ok = parseLengthPrefixedNameList(in); !ok { 104 | return 105 | } 106 | 107 | va, in, ok := parseUint64(in) 108 | if !ok { 109 | return 110 | } 111 | cert.ValidAfter = time.Unix(int64(va), 0) 112 | 113 | vb, in, ok := parseUint64(in) 114 | if !ok { 115 | return 116 | } 117 | cert.ValidBefore = time.Unix(int64(vb), 0) 118 | 119 | if cert.CriticalOptions, in, ok = parseTupleList(in); !ok { 120 | return 121 | } 122 | 123 | if cert.Extensions, in, ok = parseTupleList(in); !ok { 124 | return 125 | } 126 | 127 | if cert.Reserved, in, ok = parseString(in); !ok { 128 | return 129 | } 130 | 131 | sigKey, in, ok := parseString(in) 132 | if !ok { 133 | return 134 | } 135 | if cert.SignatureKey, _, ok = parsePubKey(sigKey); !ok { 136 | return 137 | } 138 | 139 | if cert.Signature, in, ok = parseSignature(in); !ok { 140 | return 141 | } 142 | 143 | ok = true 144 | return cert, in, ok 145 | } 146 | 147 | func marshalOpenSSHCertV01(cert *OpenSSHCertV01) []byte { 148 | var pubKey []byte 149 | switch cert.Key.(type) { 150 | case *rsa.PublicKey: 151 | k := cert.Key.(*rsa.PublicKey) 152 | pubKey = marshalPubRSA(k) 153 | case *dsa.PublicKey: 154 | k := cert.Key.(*dsa.PublicKey) 155 | pubKey = marshalPubDSA(k) 156 | case *ecdsa.PublicKey: 157 | k := cert.Key.(*ecdsa.PublicKey) 158 | pubKey = marshalPubECDSA(k) 159 | default: 160 | panic("ssh: unknown public key type in cert") 161 | } 162 | 163 | sigKey := serializePublicKey(cert.SignatureKey) 164 | 165 | length := stringLength(len(cert.Nonce)) 166 | length += len(pubKey) 167 | length += 8 // Length of Serial 168 | length += 4 // Length of Type 169 | length += stringLength(len(cert.KeyId)) 170 | length += lengthPrefixedNameListLength(cert.ValidPrincipals) 171 | length += 8 // Length of ValidAfter 172 | length += 8 // Length of ValidBefore 173 | length += tupleListLength(cert.CriticalOptions) 174 | length += tupleListLength(cert.Extensions) 175 | length += stringLength(len(cert.Reserved)) 176 | length += stringLength(len(sigKey)) 177 | length += signatureLength(cert.Signature) 178 | 179 | ret := make([]byte, length) 180 | r := marshalString(ret, cert.Nonce) 181 | copy(r, pubKey) 182 | r = r[len(pubKey):] 183 | r = marshalUint64(r, cert.Serial) 184 | r = marshalUint32(r, cert.Type) 185 | r = marshalString(r, []byte(cert.KeyId)) 186 | r = marshalLengthPrefixedNameList(r, cert.ValidPrincipals) 187 | r = marshalUint64(r, uint64(cert.ValidAfter.Unix())) 188 | r = marshalUint64(r, uint64(cert.ValidBefore.Unix())) 189 | r = marshalTupleList(r, cert.CriticalOptions) 190 | r = marshalTupleList(r, cert.Extensions) 191 | r = marshalString(r, cert.Reserved) 192 | r = marshalString(r, sigKey) 193 | r = marshalSignature(r, cert.Signature) 194 | if len(r) > 0 { 195 | panic("internal error") 196 | } 197 | return ret 198 | } 199 | 200 | func lengthPrefixedNameListLength(namelist []string) int { 201 | length := 4 // length prefix for list 202 | for _, name := range namelist { 203 | length += 4 // length prefix for name 204 | length += len(name) 205 | } 206 | return length 207 | } 208 | 209 | func marshalLengthPrefixedNameList(to []byte, namelist []string) []byte { 210 | length := uint32(lengthPrefixedNameListLength(namelist) - 4) 211 | to = marshalUint32(to, length) 212 | for _, name := range namelist { 213 | to = marshalString(to, []byte(name)) 214 | } 215 | return to 216 | } 217 | 218 | func parseLengthPrefixedNameList(in []byte) (out []string, rest []byte, ok bool) { 219 | list, rest, ok := parseString(in) 220 | if !ok { 221 | return 222 | } 223 | 224 | for len(list) > 0 { 225 | var next []byte 226 | if next, list, ok = parseString(list); !ok { 227 | return nil, nil, false 228 | } 229 | out = append(out, string(next)) 230 | } 231 | ok = true 232 | return 233 | } 234 | 235 | func tupleListLength(tupleList []tuple) int { 236 | length := 4 // length prefix for list 237 | for _, t := range tupleList { 238 | length += 4 // length prefix for t.Name 239 | length += len(t.Name) 240 | length += 4 // length prefix for t.Data 241 | length += len(t.Data) 242 | } 243 | return length 244 | } 245 | 246 | func marshalTupleList(to []byte, tuplelist []tuple) []byte { 247 | length := uint32(tupleListLength(tuplelist) - 4) 248 | to = marshalUint32(to, length) 249 | for _, t := range tuplelist { 250 | to = marshalString(to, []byte(t.Name)) 251 | to = marshalString(to, []byte(t.Data)) 252 | } 253 | return to 254 | } 255 | 256 | func parseTupleList(in []byte) (out []tuple, rest []byte, ok bool) { 257 | list, rest, ok := parseString(in) 258 | if !ok { 259 | return 260 | } 261 | 262 | for len(list) > 0 { 263 | var name, data []byte 264 | var ok bool 265 | name, list, ok = parseString(list) 266 | if !ok { 267 | return nil, nil, false 268 | } 269 | data, list, ok = parseString(list) 270 | if !ok { 271 | return nil, nil, false 272 | } 273 | out = append(out, tuple{string(name), string(data)}) 274 | } 275 | ok = true 276 | return 277 | } 278 | 279 | func signatureLength(sig *signature) int { 280 | length := 4 // length prefix for signature 281 | length += stringLength(len(sig.Format)) 282 | length += stringLength(len(sig.Blob)) 283 | return length 284 | } 285 | 286 | func marshalSignature(to []byte, sig *signature) []byte { 287 | length := uint32(signatureLength(sig) - 4) 288 | to = marshalUint32(to, length) 289 | to = marshalString(to, []byte(sig.Format)) 290 | to = marshalString(to, sig.Blob) 291 | return to 292 | } 293 | 294 | func parseSignatureBody(in []byte) (out *signature, rest []byte, ok bool) { 295 | var format []byte 296 | if format, in, ok = parseString(in); !ok { 297 | return 298 | } 299 | 300 | out = &signature{ 301 | Format: string(format), 302 | } 303 | 304 | if out.Blob, in, ok = parseString(in); !ok { 305 | return 306 | } 307 | 308 | return out, in, ok 309 | } 310 | 311 | func parseSignature(in []byte) (out *signature, rest []byte, ok bool) { 312 | var sigBytes []byte 313 | if sigBytes, rest, ok = parseString(in); !ok { 314 | return 315 | } 316 | 317 | return parseSignatureBody(sigBytes) 318 | } 319 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/cipher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "crypto/aes" 9 | "crypto/cipher" 10 | "crypto/rc4" 11 | ) 12 | 13 | // streamDump is used to dump the initial keystream for stream ciphers. It is a 14 | // a write-only buffer, and not intended for reading so do not require a mutex. 15 | var streamDump [512]byte 16 | 17 | // noneCipher implements cipher.Stream and provides no encryption. It is used 18 | // by the transport before the first key-exchange. 19 | type noneCipher struct{} 20 | 21 | func (c noneCipher) XORKeyStream(dst, src []byte) { 22 | copy(dst, src) 23 | } 24 | 25 | func newAESCTR(key, iv []byte) (cipher.Stream, error) { 26 | c, err := aes.NewCipher(key) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return cipher.NewCTR(c, iv), nil 31 | } 32 | 33 | func newRC4(key, iv []byte) (cipher.Stream, error) { 34 | return rc4.NewCipher(key) 35 | } 36 | 37 | type cipherMode struct { 38 | keySize int 39 | ivSize int 40 | skip int 41 | createFunc func(key, iv []byte) (cipher.Stream, error) 42 | } 43 | 44 | func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) { 45 | if len(key) < c.keySize { 46 | panic("ssh: key length too small for cipher") 47 | } 48 | if len(iv) < c.ivSize { 49 | panic("ssh: iv too small for cipher") 50 | } 51 | 52 | stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize]) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | for remainingToDump := c.skip; remainingToDump > 0; { 58 | dumpThisTime := remainingToDump 59 | if dumpThisTime > len(streamDump) { 60 | dumpThisTime = len(streamDump) 61 | } 62 | stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) 63 | remainingToDump -= dumpThisTime 64 | } 65 | 66 | return stream, nil 67 | } 68 | 69 | // Specifies a default set of ciphers and a preference order. This is based on 70 | // OpenSSH's default client preference order, minus algorithms that are not 71 | // implemented. 72 | var DefaultCipherOrder = []string{ 73 | "aes128-ctr", "aes192-ctr", "aes256-ctr", 74 | "arcfour256", "arcfour128", 75 | } 76 | 77 | // cipherModes documents properties of supported ciphers. Ciphers not included 78 | // are not supported and will not be negotiated, even if explicitly requested in 79 | // ClientConfig.Crypto.Ciphers. 80 | var cipherModes = map[string]*cipherMode{ 81 | // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms 82 | // are defined in the order specified in the RFC. 83 | "aes128-ctr": {16, aes.BlockSize, 0, newAESCTR}, 84 | "aes192-ctr": {24, aes.BlockSize, 0, newAESCTR}, 85 | "aes256-ctr": {32, aes.BlockSize, 0, newAESCTR}, 86 | 87 | // Ciphers from RFC4345, which introduces security-improved arcfour ciphers. 88 | // They are defined in the order specified in the RFC. 89 | "arcfour128": {16, 0, 1536, newRC4}, 90 | "arcfour256": {32, 0, 1536, newRC4}, 91 | } 92 | 93 | // defaultKeyExchangeOrder specifies a default set of key exchange algorithms 94 | // with preferences. 95 | var defaultKeyExchangeOrder = []string{ 96 | // P384 and P521 are not constant-time yet, but since we don't 97 | // reuse ephemeral keys, using them for ECDH should be OK. 98 | kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, 99 | kexAlgoDH14SHA1, kexAlgoDH1SHA1, 100 | } 101 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/cipher_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | // TestCipherReversal tests that each cipher factory produces ciphers that can 13 | // encrypt and decrypt some data successfully. 14 | func TestCipherReversal(t *testing.T) { 15 | testData := []byte("abcdefghijklmnopqrstuvwxyz012345") 16 | testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345") 17 | testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa") 18 | 19 | cryptBuffer := make([]byte, 32) 20 | 21 | for name, cipherMode := range cipherModes { 22 | encrypter, err := cipherMode.createCipher(testKey, testIv) 23 | if err != nil { 24 | t.Errorf("failed to create encrypter for %q: %s", name, err) 25 | continue 26 | } 27 | decrypter, err := cipherMode.createCipher(testKey, testIv) 28 | if err != nil { 29 | t.Errorf("failed to create decrypter for %q: %s", name, err) 30 | continue 31 | } 32 | 33 | copy(cryptBuffer, testData) 34 | 35 | encrypter.XORKeyStream(cryptBuffer, cryptBuffer) 36 | if name == "none" { 37 | if !bytes.Equal(cryptBuffer, testData) { 38 | t.Errorf("encryption made change with 'none' cipher") 39 | continue 40 | } 41 | } else { 42 | if bytes.Equal(cryptBuffer, testData) { 43 | t.Errorf("encryption made no change with %q", name) 44 | continue 45 | } 46 | } 47 | 48 | decrypter.XORKeyStream(cryptBuffer, cryptBuffer) 49 | if !bytes.Equal(cryptBuffer, testData) { 50 | t.Errorf("decrypted bytes not equal to input with %q", name) 51 | continue 52 | } 53 | } 54 | } 55 | 56 | func TestDefaultCiphersExist(t *testing.T) { 57 | for _, cipherAlgo := range DefaultCipherOrder { 58 | if _, ok := cipherModes[cipherAlgo]; !ok { 59 | t.Errorf("default cipher %q is unknown", cipherAlgo) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/client_test.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func testClientVersion(t *testing.T, config *ClientConfig, expected string) { 9 | clientConn, serverConn := net.Pipe() 10 | receivedVersion := make(chan string, 1) 11 | go func() { 12 | version, err := readVersion(serverConn) 13 | if err != nil { 14 | receivedVersion <- "" 15 | } else { 16 | receivedVersion <- string(version) 17 | } 18 | serverConn.Close() 19 | }() 20 | Client(clientConn, config) 21 | actual := <-receivedVersion 22 | if actual != expected { 23 | t.Fatalf("got %s; want %s", actual, expected) 24 | } 25 | } 26 | 27 | func TestCustomClientVersion(t *testing.T) { 28 | version := "Test-Client-Version-0.0" 29 | testClientVersion(t, &ClientConfig{ClientVersion: version}, version) 30 | } 31 | 32 | func TestDefaultClientVersion(t *testing.T) { 33 | testClientVersion(t, &ClientConfig{}, string(clientVersion)) 34 | } 35 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "crypto/dsa" 9 | "crypto/ecdsa" 10 | "crypto/elliptic" 11 | "crypto/rsa" 12 | "errors" 13 | "testing" 14 | ) 15 | 16 | func TestSafeString(t *testing.T) { 17 | strings := map[string]string{ 18 | "\x20\x0d\x0a": "\x20\x0d\x0a", 19 | "flibble": "flibble", 20 | "new\x20line": "new\x20line", 21 | "123456\x07789": "123456 789", 22 | "\t\t\x10\r\n": "\t\t \r\n", 23 | } 24 | 25 | for s, expected := range strings { 26 | actual := safeString(s) 27 | if expected != actual { 28 | t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual)) 29 | } 30 | } 31 | } 32 | 33 | func TestAlgoNameSupported(t *testing.T) { 34 | supported := map[string]interface{}{ 35 | KeyAlgoRSA: new(rsa.PublicKey), 36 | KeyAlgoDSA: new(dsa.PublicKey), 37 | KeyAlgoECDSA256: &ecdsa.PublicKey{Curve: elliptic.P256()}, 38 | KeyAlgoECDSA384: &ecdsa.PublicKey{Curve: elliptic.P384()}, 39 | KeyAlgoECDSA521: &ecdsa.PublicKey{Curve: elliptic.P521()}, 40 | CertAlgoRSAv01: &OpenSSHCertV01{Key: new(rsa.PublicKey)}, 41 | CertAlgoDSAv01: &OpenSSHCertV01{Key: new(dsa.PublicKey)}, 42 | CertAlgoECDSA256v01: &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P256()}}, 43 | CertAlgoECDSA384v01: &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P384()}}, 44 | CertAlgoECDSA521v01: &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P521()}}, 45 | } 46 | 47 | for expected, key := range supported { 48 | actual := algoName(key) 49 | if expected != actual { 50 | t.Errorf("expected: %s, actual: %s", expected, actual) 51 | } 52 | } 53 | 54 | } 55 | 56 | func TestAlgoNameNotSupported(t *testing.T) { 57 | notSupported := []interface{}{ 58 | &ecdsa.PublicKey{Curve: elliptic.P224()}, 59 | &OpenSSHCertV01{Key: &ecdsa.PublicKey{Curve: elliptic.P224()}}, 60 | } 61 | 62 | panicTest := func(key interface{}) (algo string, err error) { 63 | defer func() { 64 | if r := recover(); r != nil { 65 | err = errors.New(r.(string)) 66 | } 67 | }() 68 | algo = algoName(key) 69 | return 70 | } 71 | 72 | for _, unsupportedKey := range notSupported { 73 | if algo, err := panicTest(unsupportedKey); err == nil { 74 | t.Errorf("Expected a panic, Got: %s (for type %T)", algo, unsupportedKey) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package ssh implements an SSH client and server. 7 | 8 | SSH is a transport security protocol, an authentication protocol and a 9 | family of application protocols. The most typical application level 10 | protocol is a remote shell and this is specifically implemented. However, 11 | the multiplexed nature of SSH is exposed to users that wish to support 12 | others. 13 | 14 | References: 15 | [PROTOCOL.certkeys]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys 16 | [PROTOCOL.agent]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent 17 | [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 18 | */ 19 | package ssh 20 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | 14 | "code.google.com/p/go.crypto/ssh/terminal" 15 | ) 16 | 17 | func ExampleListen() { 18 | // An SSH server is represented by a ServerConfig, which holds 19 | // certificate details and handles authentication of ServerConns. 20 | config := &ServerConfig{ 21 | PasswordCallback: func(conn *ServerConn, user, pass string) bool { 22 | return user == "testuser" && pass == "tiger" 23 | }, 24 | } 25 | 26 | pemBytes, err := ioutil.ReadFile("id_rsa") 27 | if err != nil { 28 | panic("Failed to load private key") 29 | } 30 | if err = config.SetRSAPrivateKey(pemBytes); err != nil { 31 | panic("Failed to parse private key") 32 | } 33 | 34 | // Once a ServerConfig has been configured, connections can be 35 | // accepted. 36 | listener, err := Listen("tcp", "0.0.0.0:2022", config) 37 | if err != nil { 38 | panic("failed to listen for connection") 39 | } 40 | sConn, err := listener.Accept() 41 | if err != nil { 42 | panic("failed to accept incoming connection") 43 | } 44 | if err := sConn.Handshake(); err != nil { 45 | panic("failed to handshake") 46 | } 47 | 48 | // A ServerConn multiplexes several channels, which must 49 | // themselves be Accepted. 50 | for { 51 | // Accept reads from the connection, demultiplexes packets 52 | // to their corresponding channels and returns when a new 53 | // channel request is seen. Some goroutine must always be 54 | // calling Accept; otherwise no messages will be forwarded 55 | // to the channels. 56 | channel, err := sConn.Accept() 57 | if err != nil { 58 | panic("error from Accept") 59 | } 60 | 61 | // Channels have a type, depending on the application level 62 | // protocol intended. In the case of a shell, the type is 63 | // "session" and ServerShell may be used to present a simple 64 | // terminal interface. 65 | if channel.ChannelType() != "session" { 66 | channel.Reject(UnknownChannelType, "unknown channel type") 67 | continue 68 | } 69 | channel.Accept() 70 | 71 | term := terminal.NewTerminal(channel, "> ") 72 | serverTerm := &ServerTerminal{ 73 | Term: term, 74 | Channel: channel, 75 | } 76 | go func() { 77 | defer channel.Close() 78 | for { 79 | line, err := serverTerm.ReadLine() 80 | if err != nil { 81 | break 82 | } 83 | fmt.Println(line) 84 | } 85 | }() 86 | } 87 | } 88 | 89 | func ExampleDial() { 90 | // An SSH client is represented with a ClientConn. Currently only 91 | // the "password" authentication method is supported. 92 | // 93 | // To authenticate with the remote server you must pass at least one 94 | // implementation of ClientAuth via the Auth field in ClientConfig. 95 | config := &ClientConfig{ 96 | User: "username", 97 | Auth: []ClientAuth{ 98 | // ClientAuthPassword wraps a ClientPassword implementation 99 | // in a type that implements ClientAuth. 100 | ClientAuthPassword(password("yourpassword")), 101 | }, 102 | } 103 | client, err := Dial("tcp", "yourserver.com:22", config) 104 | if err != nil { 105 | panic("Failed to dial: " + err.Error()) 106 | } 107 | 108 | // Each ClientConn can support multiple interactive sessions, 109 | // represented by a Session. 110 | session, err := client.NewSession() 111 | if err != nil { 112 | panic("Failed to create session: " + err.Error()) 113 | } 114 | defer session.Close() 115 | 116 | // Once a Session is created, you can execute a single command on 117 | // the remote side using the Run method. 118 | var b bytes.Buffer 119 | session.Stdout = &b 120 | if err := session.Run("/usr/bin/whoami"); err != nil { 121 | panic("Failed to run: " + err.Error()) 122 | } 123 | fmt.Println(b.String()) 124 | } 125 | 126 | func ExampleClientConn_Listen() { 127 | config := &ClientConfig{ 128 | User: "username", 129 | Auth: []ClientAuth{ 130 | ClientAuthPassword(password("password")), 131 | }, 132 | } 133 | // Dial your ssh server. 134 | conn, err := Dial("tcp", "localhost:22", config) 135 | if err != nil { 136 | log.Fatalf("unable to connect: %s", err) 137 | } 138 | defer conn.Close() 139 | 140 | // Request the remote side to open port 8080 on all interfaces. 141 | l, err := conn.Listen("tcp", "0.0.0.0:8080") 142 | if err != nil { 143 | log.Fatalf("unable to register tcp forward: %v", err) 144 | } 145 | defer l.Close() 146 | 147 | // Serve HTTP with your SSH server acting as a reverse proxy. 148 | http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 149 | fmt.Fprintf(resp, "Hello world!\n") 150 | })) 151 | } 152 | 153 | func ExampleSession_RequestPty() { 154 | // Create client config 155 | config := &ClientConfig{ 156 | User: "username", 157 | Auth: []ClientAuth{ 158 | ClientAuthPassword(password("password")), 159 | }, 160 | } 161 | // Connect to ssh server 162 | conn, err := Dial("tcp", "localhost:22", config) 163 | if err != nil { 164 | log.Fatalf("unable to connect: %s", err) 165 | } 166 | defer conn.Close() 167 | // Create a session 168 | session, err := conn.NewSession() 169 | if err != nil { 170 | log.Fatalf("unable to create session: %s", err) 171 | } 172 | defer session.Close() 173 | // Set up terminal modes 174 | modes := TerminalModes{ 175 | ECHO: 0, // disable echoing 176 | TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud 177 | TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud 178 | } 179 | // Request pseudo terminal 180 | if err := session.RequestPty("xterm", 80, 40, modes); err != nil { 181 | log.Fatalf("request for pseudo terminal failed: %s", err) 182 | } 183 | // Start remote shell 184 | if err := session.Shell(); err != nil { 185 | log.Fatalf("failed to start shell: %s", err) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/kex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | // Key exchange tests. 8 | 9 | import ( 10 | "fmt" 11 | "net" 12 | "testing" 13 | ) 14 | 15 | func pipe() (net.Conn, net.Conn, error) { 16 | l, err := net.Listen("tcp", "127.0.0.1:0") 17 | if err != nil { 18 | return nil, nil, err 19 | } 20 | conn1, err := net.Dial("tcp", l.Addr().String()) 21 | if err != nil { 22 | return nil, nil, err 23 | } 24 | 25 | conn2, err := l.Accept() 26 | if err != nil { 27 | conn1.Close() 28 | return nil, nil, err 29 | } 30 | l.Close() 31 | return conn1, conn2, nil 32 | } 33 | 34 | func testKexAlgorithm(algo string) error { 35 | crypto := CryptoConfig{ 36 | KeyExchanges: []string{algo}, 37 | } 38 | serverConfig := ServerConfig{ 39 | PasswordCallback: func(conn *ServerConn, user, password string) bool { 40 | return password == "password" 41 | }, 42 | Crypto: crypto, 43 | } 44 | 45 | if err := serverConfig.SetRSAPrivateKey([]byte(testServerPrivateKey)); err != nil { 46 | return fmt.Errorf("SetRSAPrivateKey: %v", err) 47 | } 48 | 49 | clientConfig := ClientConfig{ 50 | User: "user", 51 | Auth: []ClientAuth{ClientAuthPassword(password("password"))}, 52 | Crypto: crypto, 53 | } 54 | 55 | conn1, conn2, err := pipe() 56 | if err != nil { 57 | return err 58 | } 59 | 60 | defer conn1.Close() 61 | defer conn2.Close() 62 | 63 | server := Server(conn2, &serverConfig) 64 | serverHS := make(chan error, 1) 65 | go func() { 66 | serverHS <- server.Handshake() 67 | }() 68 | 69 | // Client runs the handshake. 70 | _, err = Client(conn1, &clientConfig) 71 | if err != nil { 72 | return fmt.Errorf("Client: %v", err) 73 | } 74 | 75 | if err := <-serverHS; err != nil { 76 | return fmt.Errorf("server.Handshake: %v", err) 77 | } 78 | 79 | // Here we could check that we now can send data between client & 80 | // server. 81 | return nil 82 | } 83 | 84 | func TestKexAlgorithms(t *testing.T) { 85 | for _, algo := range []string{kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, kexAlgoDH1SHA1, kexAlgoDH14SHA1} { 86 | if err := testKexAlgorithm(algo); err != nil { 87 | t.Errorf("algorithm %s: %v", algo, err) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "bytes" 9 | "crypto/dsa" 10 | "crypto/ecdsa" 11 | "crypto/elliptic" 12 | "crypto/rsa" 13 | "encoding/base64" 14 | "math/big" 15 | ) 16 | 17 | // These constants represent the algorithm names for key types supported by this 18 | // package. 19 | const ( 20 | KeyAlgoRSA = "ssh-rsa" 21 | KeyAlgoDSA = "ssh-dss" 22 | KeyAlgoECDSA256 = "ecdsa-sha2-nistp256" 23 | KeyAlgoECDSA384 = "ecdsa-sha2-nistp384" 24 | KeyAlgoECDSA521 = "ecdsa-sha2-nistp521" 25 | ) 26 | 27 | // parsePubKey parses a public key according to RFC 4253, section 6.6. 28 | func parsePubKey(in []byte) (out interface{}, rest []byte, ok bool) { 29 | algo, in, ok := parseString(in) 30 | if !ok { 31 | return 32 | } 33 | 34 | switch string(algo) { 35 | case KeyAlgoRSA: 36 | return parseRSA(in) 37 | case KeyAlgoDSA: 38 | return parseDSA(in) 39 | case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: 40 | return parseECDSA(in) 41 | case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: 42 | return parseOpenSSHCertV01(in, string(algo)) 43 | } 44 | panic("ssh: unknown public key type") 45 | } 46 | 47 | // parseRSA parses an RSA key according to RFC 4253, section 6.6. 48 | func parseRSA(in []byte) (out *rsa.PublicKey, rest []byte, ok bool) { 49 | key := new(rsa.PublicKey) 50 | 51 | bigE, in, ok := parseInt(in) 52 | if !ok || bigE.BitLen() > 24 { 53 | return 54 | } 55 | e := bigE.Int64() 56 | if e < 3 || e&1 == 0 { 57 | ok = false 58 | return 59 | } 60 | key.E = int(e) 61 | 62 | if key.N, in, ok = parseInt(in); !ok { 63 | return 64 | } 65 | 66 | ok = true 67 | return key, in, ok 68 | } 69 | 70 | // parseDSA parses an DSA key according to RFC 4253, section 6.6. 71 | func parseDSA(in []byte) (out *dsa.PublicKey, rest []byte, ok bool) { 72 | key := new(dsa.PublicKey) 73 | 74 | if key.P, in, ok = parseInt(in); !ok { 75 | return 76 | } 77 | 78 | if key.Q, in, ok = parseInt(in); !ok { 79 | return 80 | } 81 | 82 | if key.G, in, ok = parseInt(in); !ok { 83 | return 84 | } 85 | 86 | if key.Y, in, ok = parseInt(in); !ok { 87 | return 88 | } 89 | 90 | ok = true 91 | return key, in, ok 92 | } 93 | 94 | // parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. 95 | func parseECDSA(in []byte) (out *ecdsa.PublicKey, rest []byte, ok bool) { 96 | var identifier []byte 97 | if identifier, in, ok = parseString(in); !ok { 98 | return 99 | } 100 | 101 | key := new(ecdsa.PublicKey) 102 | 103 | switch string(identifier) { 104 | case "nistp256": 105 | key.Curve = elliptic.P256() 106 | case "nistp384": 107 | key.Curve = elliptic.P384() 108 | case "nistp521": 109 | key.Curve = elliptic.P521() 110 | default: 111 | ok = false 112 | return 113 | } 114 | 115 | var keyBytes []byte 116 | if keyBytes, in, ok = parseString(in); !ok { 117 | return 118 | } 119 | 120 | key.X, key.Y = elliptic.Unmarshal(key.Curve, keyBytes) 121 | if key.X == nil || key.Y == nil { 122 | ok = false 123 | return 124 | } 125 | return key, in, ok 126 | } 127 | 128 | // marshalPubRSA serializes an RSA public key according to RFC 4253, section 6.6. 129 | func marshalPubRSA(key *rsa.PublicKey) []byte { 130 | e := new(big.Int).SetInt64(int64(key.E)) 131 | length := intLength(e) 132 | length += intLength(key.N) 133 | 134 | ret := make([]byte, length) 135 | r := marshalInt(ret, e) 136 | r = marshalInt(r, key.N) 137 | 138 | return ret 139 | } 140 | 141 | // marshalPubDSA serializes an DSA public key according to RFC 4253, section 6.6. 142 | func marshalPubDSA(key *dsa.PublicKey) []byte { 143 | length := intLength(key.P) 144 | length += intLength(key.Q) 145 | length += intLength(key.G) 146 | length += intLength(key.Y) 147 | 148 | ret := make([]byte, length) 149 | r := marshalInt(ret, key.P) 150 | r = marshalInt(r, key.Q) 151 | r = marshalInt(r, key.G) 152 | r = marshalInt(r, key.Y) 153 | 154 | return ret 155 | } 156 | 157 | // marshalPubECDSA serializes an ECDSA public key according to RFC 5656, section 3.1. 158 | func marshalPubECDSA(key *ecdsa.PublicKey) []byte { 159 | var identifier []byte 160 | switch key.Params().BitSize { 161 | case 256: 162 | identifier = []byte("nistp256") 163 | case 384: 164 | identifier = []byte("nistp384") 165 | case 521: 166 | identifier = []byte("nistp521") 167 | default: 168 | panic("ssh: unsupported ecdsa key size") 169 | } 170 | keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y) 171 | 172 | length := stringLength(len(identifier)) 173 | length += stringLength(len(keyBytes)) 174 | 175 | ret := make([]byte, length) 176 | r := marshalString(ret, identifier) 177 | r = marshalString(r, keyBytes) 178 | return ret 179 | } 180 | 181 | // parseAuthorizedKey parses a public key in OpenSSH authorized_keys format 182 | // (see sshd(8) manual page) once the options and key type fields have been 183 | // removed. 184 | func parseAuthorizedKey(in []byte) (out interface{}, comment string, ok bool) { 185 | in = bytes.TrimSpace(in) 186 | 187 | i := bytes.IndexAny(in, " \t") 188 | if i == -1 { 189 | i = len(in) 190 | } 191 | base64Key := in[:i] 192 | 193 | key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) 194 | n, err := base64.StdEncoding.Decode(key, base64Key) 195 | if err != nil { 196 | return 197 | } 198 | key = key[:n] 199 | out, _, ok = parsePubKey(key) 200 | if !ok { 201 | return nil, "", false 202 | } 203 | comment = string(bytes.TrimSpace(in[i:])) 204 | return 205 | } 206 | 207 | // ParseAuthorizedKeys parses a public key from an authorized_keys 208 | // file used in OpenSSH according to the sshd(8) manual page. 209 | func ParseAuthorizedKey(in []byte) (out interface{}, comment string, options []string, rest []byte, ok bool) { 210 | for len(in) > 0 { 211 | end := bytes.IndexByte(in, '\n') 212 | if end != -1 { 213 | rest = in[end+1:] 214 | in = in[:end] 215 | } else { 216 | rest = nil 217 | } 218 | 219 | end = bytes.IndexByte(in, '\r') 220 | if end != -1 { 221 | in = in[:end] 222 | } 223 | 224 | in = bytes.TrimSpace(in) 225 | if len(in) == 0 || in[0] == '#' { 226 | in = rest 227 | continue 228 | } 229 | 230 | i := bytes.IndexAny(in, " \t") 231 | if i == -1 { 232 | in = rest 233 | continue 234 | } 235 | 236 | field := string(in[:i]) 237 | switch field { 238 | case KeyAlgoRSA, KeyAlgoDSA: 239 | out, comment, ok = parseAuthorizedKey(in[i:]) 240 | if ok { 241 | return 242 | } 243 | case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: 244 | // We don't support these keys. 245 | in = rest 246 | continue 247 | case CertAlgoRSAv01, CertAlgoDSAv01, 248 | CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: 249 | // We don't support these certificates. 250 | in = rest 251 | continue 252 | } 253 | 254 | // No key type recognised. Maybe there's an options field at 255 | // the beginning. 256 | var b byte 257 | inQuote := false 258 | var candidateOptions []string 259 | optionStart := 0 260 | for i, b = range in { 261 | isEnd := !inQuote && (b == ' ' || b == '\t') 262 | if (b == ',' && !inQuote) || isEnd { 263 | if i-optionStart > 0 { 264 | candidateOptions = append(candidateOptions, string(in[optionStart:i])) 265 | } 266 | optionStart = i + 1 267 | } 268 | if isEnd { 269 | break 270 | } 271 | if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) { 272 | inQuote = !inQuote 273 | } 274 | } 275 | for i < len(in) && (in[i] == ' ' || in[i] == '\t') { 276 | i++ 277 | } 278 | if i == len(in) { 279 | // Invalid line: unmatched quote 280 | in = rest 281 | continue 282 | } 283 | 284 | in = in[i:] 285 | i = bytes.IndexAny(in, " \t") 286 | if i == -1 { 287 | in = rest 288 | continue 289 | } 290 | 291 | field = string(in[:i]) 292 | switch field { 293 | case KeyAlgoRSA, KeyAlgoDSA: 294 | out, comment, ok = parseAuthorizedKey(in[i:]) 295 | if ok { 296 | options = candidateOptions 297 | return 298 | } 299 | } 300 | 301 | in = rest 302 | continue 303 | } 304 | 305 | return 306 | } 307 | 308 | // ParsePublicKey parses an SSH public key formatted for use in 309 | // the SSH wire protocol. 310 | func ParsePublicKey(in []byte) (out interface{}, rest []byte, ok bool) { 311 | return parsePubKey(in) 312 | } 313 | 314 | // MarshalAuthorizedKey returns a byte stream suitable for inclusion 315 | // in an OpenSSH authorized_keys file following the format specified 316 | // in the sshd(8) manual page. 317 | func MarshalAuthorizedKey(key interface{}) []byte { 318 | b := &bytes.Buffer{} 319 | b.WriteString(algoName(key)) 320 | b.WriteByte(' ') 321 | e := base64.NewEncoder(base64.StdEncoding, b) 322 | e.Write(serializePublicKey(key)) 323 | e.Close() 324 | b.WriteByte('\n') 325 | return b.Bytes() 326 | } 327 | 328 | // MarshalPublicKey serializes a supported key or certificate for use by the 329 | // SSH wire protocol. It can be used for comparison with the pubkey argument 330 | // of ServerConfig's PublicKeyCallback as well as for generating an 331 | // authorized_keys or host_keys file. 332 | func MarshalPublicKey(key interface{}) []byte { 333 | return serializePublicKey(key) 334 | } 335 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/mac.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | // Message authentication support 8 | 9 | import ( 10 | "crypto/hmac" 11 | "crypto/sha1" 12 | "hash" 13 | ) 14 | 15 | type macMode struct { 16 | keySize int 17 | new func(key []byte) hash.Hash 18 | } 19 | 20 | // truncatingMAC wraps around a hash.Hash and truncates the output digest to 21 | // a given size. 22 | type truncatingMAC struct { 23 | length int 24 | hmac hash.Hash 25 | } 26 | 27 | func (t truncatingMAC) Write(data []byte) (int, error) { 28 | return t.hmac.Write(data) 29 | } 30 | 31 | func (t truncatingMAC) Sum(in []byte) []byte { 32 | out := t.hmac.Sum(in) 33 | return out[:len(in)+t.length] 34 | } 35 | 36 | func (t truncatingMAC) Reset() { 37 | t.hmac.Reset() 38 | } 39 | 40 | func (t truncatingMAC) Size() int { 41 | return t.length 42 | } 43 | 44 | func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } 45 | 46 | // Specifies a default set of MAC algorithms and a preference order. 47 | // This is based on RFC 4253, section 6.4, with the removal of the 48 | // hmac-md5 variants as they have reached the end of their useful life. 49 | var DefaultMACOrder = []string{"hmac-sha1", "hmac-sha1-96"} 50 | 51 | var macModes = map[string]*macMode{ 52 | "hmac-sha1": {20, func(key []byte) hash.Hash { 53 | return hmac.New(sha1.New, key) 54 | }}, 55 | "hmac-sha1-96": {20, func(key []byte) hash.Hash { 56 | return truncatingMAC{12, hmac.New(sha1.New, key)} 57 | }}, 58 | } 59 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "math/big" 9 | "math/rand" 10 | "reflect" 11 | "testing" 12 | "testing/quick" 13 | ) 14 | 15 | var intLengthTests = []struct { 16 | val, length int 17 | }{ 18 | {0, 4 + 0}, 19 | {1, 4 + 1}, 20 | {127, 4 + 1}, 21 | {128, 4 + 2}, 22 | {-1, 4 + 1}, 23 | } 24 | 25 | func TestIntLength(t *testing.T) { 26 | for _, test := range intLengthTests { 27 | v := new(big.Int).SetInt64(int64(test.val)) 28 | length := intLength(v) 29 | if length != test.length { 30 | t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length) 31 | } 32 | } 33 | } 34 | 35 | var messageTypes = []interface{}{ 36 | &kexInitMsg{}, 37 | &kexDHInitMsg{}, 38 | &serviceRequestMsg{}, 39 | &serviceAcceptMsg{}, 40 | &userAuthRequestMsg{}, 41 | &channelOpenMsg{}, 42 | &channelOpenConfirmMsg{}, 43 | &channelOpenFailureMsg{}, 44 | &channelRequestMsg{}, 45 | &channelRequestSuccessMsg{}, 46 | } 47 | 48 | func TestMarshalUnmarshal(t *testing.T) { 49 | rand := rand.New(rand.NewSource(0)) 50 | for i, iface := range messageTypes { 51 | ty := reflect.ValueOf(iface).Type() 52 | 53 | n := 100 54 | if testing.Short() { 55 | n = 5 56 | } 57 | for j := 0; j < n; j++ { 58 | v, ok := quick.Value(ty, rand) 59 | if !ok { 60 | t.Errorf("#%d: failed to create value", i) 61 | break 62 | } 63 | 64 | m1 := v.Elem().Interface() 65 | m2 := iface 66 | 67 | marshaled := marshal(msgIgnore, m1) 68 | if err := unmarshal(m2, marshaled, msgIgnore); err != nil { 69 | t.Errorf("#%d failed to unmarshal %#v: %s", i, m1, err) 70 | break 71 | } 72 | 73 | if !reflect.DeepEqual(v.Interface(), m2) { 74 | t.Errorf("#%d\ngot: %#v\nwant:%#v\n%x", i, m2, m1, marshaled) 75 | break 76 | } 77 | } 78 | } 79 | } 80 | 81 | func randomBytes(out []byte, rand *rand.Rand) { 82 | for i := 0; i < len(out); i++ { 83 | out[i] = byte(rand.Int31()) 84 | } 85 | } 86 | 87 | func randomNameList(rand *rand.Rand) []string { 88 | ret := make([]string, rand.Int31()&15) 89 | for i := range ret { 90 | s := make([]byte, 1+(rand.Int31()&15)) 91 | for j := range s { 92 | s[j] = 'a' + uint8(rand.Int31()&15) 93 | } 94 | ret[i] = string(s) 95 | } 96 | return ret 97 | } 98 | 99 | func randomInt(rand *rand.Rand) *big.Int { 100 | return new(big.Int).SetInt64(int64(int32(rand.Uint32()))) 101 | } 102 | 103 | func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { 104 | ki := &kexInitMsg{} 105 | randomBytes(ki.Cookie[:], rand) 106 | ki.KexAlgos = randomNameList(rand) 107 | ki.ServerHostKeyAlgos = randomNameList(rand) 108 | ki.CiphersClientServer = randomNameList(rand) 109 | ki.CiphersServerClient = randomNameList(rand) 110 | ki.MACsClientServer = randomNameList(rand) 111 | ki.MACsServerClient = randomNameList(rand) 112 | ki.CompressionClientServer = randomNameList(rand) 113 | ki.CompressionServerClient = randomNameList(rand) 114 | ki.LanguagesClientServer = randomNameList(rand) 115 | ki.LanguagesServerClient = randomNameList(rand) 116 | if rand.Int31()&1 == 1 { 117 | ki.FirstKexFollows = true 118 | } 119 | return reflect.ValueOf(ki) 120 | } 121 | 122 | func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { 123 | dhi := &kexDHInitMsg{} 124 | dhi.X = randomInt(rand) 125 | return reflect.ValueOf(dhi) 126 | } 127 | 128 | // TODO(dfc) maybe this can be removed in the future if testing/quick can handle 129 | // derived basic types. 130 | func (RejectionReason) Generate(rand *rand.Rand, size int) reflect.Value { 131 | m := RejectionReason(Prohibited) 132 | return reflect.ValueOf(m) 133 | } 134 | 135 | var ( 136 | _kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() 137 | _kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() 138 | 139 | _kexInit = marshal(msgKexInit, _kexInitMsg) 140 | _kexDHInit = marshal(msgKexDHInit, _kexDHInitMsg) 141 | ) 142 | 143 | func BenchmarkMarshalKexInitMsg(b *testing.B) { 144 | for i := 0; i < b.N; i++ { 145 | marshal(msgKexInit, _kexInitMsg) 146 | } 147 | } 148 | 149 | func BenchmarkUnmarshalKexInitMsg(b *testing.B) { 150 | m := new(kexInitMsg) 151 | for i := 0; i < b.N; i++ { 152 | unmarshal(m, _kexInit, msgKexInit) 153 | } 154 | } 155 | 156 | func BenchmarkMarshalKexDHInitMsg(b *testing.B) { 157 | for i := 0; i < b.N; i++ { 158 | marshal(msgKexDHInit, _kexDHInitMsg) 159 | } 160 | } 161 | 162 | func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) { 163 | m := new(kexDHInitMsg) 164 | for i := 0; i < b.N; i++ { 165 | unmarshal(m, _kexDHInit, msgKexDHInit) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/server_terminal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | // A Terminal is capable of parsing and generating virtual terminal 8 | // data from an SSH client. 9 | type Terminal interface { 10 | ReadLine() (line string, err error) 11 | SetSize(x, y int) 12 | Write([]byte) (int, error) 13 | } 14 | 15 | // ServerTerminal contains the state for running a terminal that is capable of 16 | // reading lines of input. 17 | type ServerTerminal struct { 18 | Term Terminal 19 | Channel Channel 20 | } 21 | 22 | // parsePtyRequest parses the payload of the pty-req message and extracts the 23 | // dimensions of the terminal. See RFC 4254, section 6.2. 24 | func parsePtyRequest(s []byte) (width, height int, ok bool) { 25 | _, s, ok = parseString(s) 26 | if !ok { 27 | return 28 | } 29 | width32, s, ok := parseUint32(s) 30 | if !ok { 31 | return 32 | } 33 | height32, _, ok := parseUint32(s) 34 | width = int(width32) 35 | height = int(height32) 36 | if width < 1 { 37 | ok = false 38 | } 39 | if height < 1 { 40 | ok = false 41 | } 42 | return 43 | } 44 | 45 | func (ss *ServerTerminal) Write(buf []byte) (n int, err error) { 46 | return ss.Term.Write(buf) 47 | } 48 | 49 | // ReadLine returns a line of input from the terminal. 50 | func (ss *ServerTerminal) ReadLine() (line string, err error) { 51 | for { 52 | if line, err = ss.Term.ReadLine(); err == nil { 53 | return 54 | } 55 | 56 | req, ok := err.(ChannelRequest) 57 | if !ok { 58 | return 59 | } 60 | 61 | ok = false 62 | switch req.Request { 63 | case "pty-req": 64 | var width, height int 65 | width, height, ok = parsePtyRequest(req.Payload) 66 | ss.Term.SetSize(width, height) 67 | case "shell": 68 | ok = true 69 | if len(req.Payload) > 0 { 70 | // We don't accept any commands, only the default shell. 71 | ok = false 72 | } 73 | case "env": 74 | ok = true 75 | } 76 | if req.WantReply { 77 | ss.Channel.AckRequest(ok) 78 | } 79 | } 80 | panic("unreachable") 81 | } 82 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/tcpip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | "math/rand" 12 | "net" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | // Listen requests the remote peer open a listening socket 20 | // on addr. Incoming connections will be available by calling 21 | // Accept on the returned net.Listener. 22 | func (c *ClientConn) Listen(n, addr string) (net.Listener, error) { 23 | laddr, err := net.ResolveTCPAddr(n, addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return c.ListenTCP(laddr) 28 | } 29 | 30 | // RFC 4254 7.1 31 | type channelForwardMsg struct { 32 | Message string 33 | WantReply bool 34 | raddr string 35 | rport uint32 36 | } 37 | 38 | // Automatic port allocation is broken with OpenSSH before 6.0. See 39 | // also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In 40 | // particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0, 41 | // rather than the actual port number. This means you can never open 42 | // two different listeners with auto allocated ports. We work around 43 | // this by trying explicit ports until we succeed. 44 | 45 | const openSSHPrefix = "OpenSSH_" 46 | 47 | var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano())) 48 | 49 | // isBrokenOpenSSHVersion returns true if the given version string 50 | // specifies a version of OpenSSH that is known to have a bug in port 51 | // forwarding. 52 | func isBrokenOpenSSHVersion(versionStr string) bool { 53 | i := strings.Index(versionStr, openSSHPrefix) 54 | if i < 0 { 55 | return false 56 | } 57 | i += len(openSSHPrefix) 58 | j := i 59 | for ; j < len(versionStr); j++ { 60 | if versionStr[j] < '0' || versionStr[j] > '9' { 61 | break 62 | } 63 | } 64 | version, _ := strconv.Atoi(versionStr[i:j]) 65 | return version < 6 66 | } 67 | 68 | // autoPortListenWorkaround simulates automatic port allocation by 69 | // trying random ports repeatedly. 70 | func (c *ClientConn) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) { 71 | var sshListener net.Listener 72 | var err error 73 | const tries = 10 74 | for i := 0; i < tries; i++ { 75 | addr := *laddr 76 | addr.Port = 1024 + portRandomizer.Intn(60000) 77 | sshListener, err = c.ListenTCP(&addr) 78 | if err == nil { 79 | laddr.Port = addr.Port 80 | return sshListener, err 81 | } 82 | } 83 | return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err) 84 | } 85 | 86 | // ListenTCP requests the remote peer open a listening socket 87 | // on laddr. Incoming connections will be available by calling 88 | // Accept on the returned net.Listener. 89 | func (c *ClientConn) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { 90 | if laddr.Port == 0 && isBrokenOpenSSHVersion(c.serverVersion) { 91 | return c.autoPortListenWorkaround(laddr) 92 | } 93 | 94 | m := channelForwardMsg{ 95 | "tcpip-forward", 96 | true, // sendGlobalRequest waits for a reply 97 | laddr.IP.String(), 98 | uint32(laddr.Port), 99 | } 100 | // send message 101 | resp, err := c.sendGlobalRequest(m) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | // If the original port was 0, then the remote side will 107 | // supply a real port number in the response. 108 | if laddr.Port == 0 { 109 | port, _, ok := parseUint32(resp.Data) 110 | if !ok { 111 | return nil, errors.New("unable to parse response") 112 | } 113 | laddr.Port = int(port) 114 | } 115 | 116 | // Register this forward, using the port number we obtained. 117 | ch := c.forwardList.add(*laddr) 118 | 119 | return &tcpListener{laddr, c, ch}, nil 120 | } 121 | 122 | // forwardList stores a mapping between remote 123 | // forward requests and the tcpListeners. 124 | type forwardList struct { 125 | sync.Mutex 126 | entries []forwardEntry 127 | } 128 | 129 | // forwardEntry represents an established mapping of a laddr on a 130 | // remote ssh server to a channel connected to a tcpListener. 131 | type forwardEntry struct { 132 | laddr net.TCPAddr 133 | c chan forward 134 | } 135 | 136 | // forward represents an incoming forwarded tcpip connection. The 137 | // arguments to add/remove/lookup should be address as specified in 138 | // the original forward-request. 139 | type forward struct { 140 | c *clientChan // the ssh client channel underlying this forward 141 | raddr *net.TCPAddr // the raddr of the incoming connection 142 | } 143 | 144 | func (l *forwardList) add(addr net.TCPAddr) chan forward { 145 | l.Lock() 146 | defer l.Unlock() 147 | f := forwardEntry{ 148 | addr, 149 | make(chan forward, 1), 150 | } 151 | l.entries = append(l.entries, f) 152 | return f.c 153 | } 154 | 155 | // remove removes the forward entry, and the channel feeding its 156 | // listener. 157 | func (l *forwardList) remove(addr net.TCPAddr) { 158 | l.Lock() 159 | defer l.Unlock() 160 | for i, f := range l.entries { 161 | if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port { 162 | l.entries = append(l.entries[:i], l.entries[i+1:]...) 163 | close(f.c) 164 | return 165 | } 166 | } 167 | } 168 | 169 | // closeAll closes and clears all forwards. 170 | func (l *forwardList) closeAll() { 171 | l.Lock() 172 | defer l.Unlock() 173 | for _, f := range l.entries { 174 | close(f.c) 175 | } 176 | l.entries = nil 177 | } 178 | 179 | func (l *forwardList) lookup(addr net.TCPAddr) (chan forward, bool) { 180 | l.Lock() 181 | defer l.Unlock() 182 | for _, f := range l.entries { 183 | if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port { 184 | return f.c, true 185 | } 186 | } 187 | return nil, false 188 | } 189 | 190 | type tcpListener struct { 191 | laddr *net.TCPAddr 192 | 193 | conn *ClientConn 194 | in <-chan forward 195 | } 196 | 197 | // Accept waits for and returns the next connection to the listener. 198 | func (l *tcpListener) Accept() (net.Conn, error) { 199 | s, ok := <-l.in 200 | if !ok { 201 | return nil, io.EOF 202 | } 203 | return &tcpChanConn{ 204 | tcpChan: &tcpChan{ 205 | clientChan: s.c, 206 | Reader: s.c.stdout, 207 | Writer: s.c.stdin, 208 | }, 209 | laddr: l.laddr, 210 | raddr: s.raddr, 211 | }, nil 212 | } 213 | 214 | // Close closes the listener. 215 | func (l *tcpListener) Close() error { 216 | m := channelForwardMsg{ 217 | "cancel-tcpip-forward", 218 | true, 219 | l.laddr.IP.String(), 220 | uint32(l.laddr.Port), 221 | } 222 | l.conn.forwardList.remove(*l.laddr) 223 | if _, err := l.conn.sendGlobalRequest(m); err != nil { 224 | return err 225 | } 226 | return nil 227 | } 228 | 229 | // Addr returns the listener's network address. 230 | func (l *tcpListener) Addr() net.Addr { 231 | return l.laddr 232 | } 233 | 234 | // Dial initiates a connection to the addr from the remote host. 235 | // The resulting connection has a zero LocalAddr() and RemoteAddr(). 236 | func (c *ClientConn) Dial(n, addr string) (net.Conn, error) { 237 | // Parse the address into host and numeric port. 238 | host, portString, err := net.SplitHostPort(addr) 239 | if err != nil { 240 | return nil, err 241 | } 242 | port, err := strconv.ParseUint(portString, 10, 16) 243 | if err != nil { 244 | return nil, err 245 | } 246 | // Use a zero address for local and remote address. 247 | zeroAddr := &net.TCPAddr{ 248 | IP: net.IPv4zero, 249 | Port: 0, 250 | } 251 | ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port)) 252 | if err != nil { 253 | return nil, err 254 | } 255 | return &tcpChanConn{ 256 | tcpChan: ch, 257 | laddr: zeroAddr, 258 | raddr: zeroAddr, 259 | }, nil 260 | } 261 | 262 | // DialTCP connects to the remote address raddr on the network net, 263 | // which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used 264 | // as the local address for the connection. 265 | func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) { 266 | if laddr == nil { 267 | laddr = &net.TCPAddr{ 268 | IP: net.IPv4zero, 269 | Port: 0, 270 | } 271 | } 272 | ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port) 273 | if err != nil { 274 | return nil, err 275 | } 276 | return &tcpChanConn{ 277 | tcpChan: ch, 278 | laddr: laddr, 279 | raddr: raddr, 280 | }, nil 281 | } 282 | 283 | // RFC 4254 7.2 284 | type channelOpenDirectMsg struct { 285 | ChanType string 286 | PeersId uint32 287 | PeersWindow uint32 288 | MaxPacketSize uint32 289 | raddr string 290 | rport uint32 291 | laddr string 292 | lport uint32 293 | } 294 | 295 | // dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as 296 | // strings and are expected to be resolveable at the remote end. 297 | func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpChan, error) { 298 | ch := c.newChan(c.transport) 299 | if err := c.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{ 300 | ChanType: "direct-tcpip", 301 | PeersId: ch.localId, 302 | PeersWindow: 1 << 14, 303 | MaxPacketSize: 1 << 15, // RFC 4253 6.1 304 | raddr: raddr, 305 | rport: uint32(rport), 306 | laddr: laddr, 307 | lport: uint32(lport), 308 | })); err != nil { 309 | c.chanList.remove(ch.localId) 310 | return nil, err 311 | } 312 | if err := ch.waitForChannelOpenResponse(); err != nil { 313 | c.chanList.remove(ch.localId) 314 | return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err) 315 | } 316 | return &tcpChan{ 317 | clientChan: ch, 318 | Reader: ch.stdout, 319 | Writer: ch.stdin, 320 | }, nil 321 | } 322 | 323 | type tcpChan struct { 324 | *clientChan // the backing channel 325 | io.Reader 326 | io.Writer 327 | } 328 | 329 | // tcpChanConn fulfills the net.Conn interface without 330 | // the tcpChan having to hold laddr or raddr directly. 331 | type tcpChanConn struct { 332 | *tcpChan 333 | laddr, raddr net.Addr 334 | } 335 | 336 | // LocalAddr returns the local network address. 337 | func (t *tcpChanConn) LocalAddr() net.Addr { 338 | return t.laddr 339 | } 340 | 341 | // RemoteAddr returns the remote network address. 342 | func (t *tcpChanConn) RemoteAddr() net.Addr { 343 | return t.raddr 344 | } 345 | 346 | // SetDeadline sets the read and write deadlines associated 347 | // with the connection. 348 | func (t *tcpChanConn) SetDeadline(deadline time.Time) error { 349 | if err := t.SetReadDeadline(deadline); err != nil { 350 | return err 351 | } 352 | return t.SetWriteDeadline(deadline) 353 | } 354 | 355 | // SetReadDeadline sets the read deadline. 356 | // A zero value for t means Read will not time out. 357 | // After the deadline, the error from Read will implement net.Error 358 | // with Timeout() == true. 359 | func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error { 360 | return errors.New("ssh: tcpChan: deadline not supported") 361 | } 362 | 363 | // SetWriteDeadline exists to satisfy the net.Conn interface 364 | // but is not implemented by this type. It always returns an error. 365 | func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error { 366 | return errors.New("ssh: tcpChan: deadline not supported") 367 | } 368 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/tcpip_test.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAutoPortListenBroken(t *testing.T) { 8 | broken := "SSH-2.0-OpenSSH_5.9hh11" 9 | works := "SSH-2.0-OpenSSH_6.1" 10 | if !isBrokenOpenSSHVersion(broken) { 11 | t.Errorf("version %q not marked as broken", broken) 12 | } 13 | if isBrokenOpenSSHVersion(works) { 14 | t.Errorf("version %q marked as broken", works) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/terminal/terminal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package terminal 6 | 7 | import ( 8 | "io" 9 | "testing" 10 | ) 11 | 12 | type MockTerminal struct { 13 | toSend []byte 14 | bytesPerRead int 15 | received []byte 16 | } 17 | 18 | func (c *MockTerminal) Read(data []byte) (n int, err error) { 19 | n = len(data) 20 | if n == 0 { 21 | return 22 | } 23 | if n > len(c.toSend) { 24 | n = len(c.toSend) 25 | } 26 | if n == 0 { 27 | return 0, io.EOF 28 | } 29 | if c.bytesPerRead > 0 && n > c.bytesPerRead { 30 | n = c.bytesPerRead 31 | } 32 | copy(data, c.toSend[:n]) 33 | c.toSend = c.toSend[n:] 34 | return 35 | } 36 | 37 | func (c *MockTerminal) Write(data []byte) (n int, err error) { 38 | c.received = append(c.received, data...) 39 | return len(data), nil 40 | } 41 | 42 | func TestClose(t *testing.T) { 43 | c := &MockTerminal{} 44 | ss := NewTerminal(c, "> ") 45 | line, err := ss.ReadLine() 46 | if line != "" { 47 | t.Errorf("Expected empty line but got: %s", line) 48 | } 49 | if err != io.EOF { 50 | t.Errorf("Error should have been EOF but got: %s", err) 51 | } 52 | } 53 | 54 | var keyPressTests = []struct { 55 | in string 56 | line string 57 | err error 58 | throwAwayLines int 59 | }{ 60 | { 61 | err: io.EOF, 62 | }, 63 | { 64 | in: "\r", 65 | line: "", 66 | }, 67 | { 68 | in: "foo\r", 69 | line: "foo", 70 | }, 71 | { 72 | in: "a\x1b[Cb\r", // right 73 | line: "ab", 74 | }, 75 | { 76 | in: "a\x1b[Db\r", // left 77 | line: "ba", 78 | }, 79 | { 80 | in: "a\177b\r", // backspace 81 | line: "b", 82 | }, 83 | { 84 | in: "\x1b[A\r", // up 85 | }, 86 | { 87 | in: "\x1b[B\r", // down 88 | }, 89 | { 90 | in: "line\x1b[A\x1b[B\r", // up then down 91 | line: "line", 92 | }, 93 | { 94 | in: "line1\rline2\x1b[A\r", // recall previous line. 95 | line: "line1", 96 | throwAwayLines: 1, 97 | }, 98 | { 99 | // recall two previous lines and append. 100 | in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r", 101 | line: "line1xxx", 102 | throwAwayLines: 2, 103 | }, 104 | { 105 | in: "\027\r", 106 | line: "", 107 | }, 108 | { 109 | in: "a\027\r", 110 | line: "", 111 | }, 112 | { 113 | in: "a \027\r", 114 | line: "", 115 | }, 116 | { 117 | in: "a b\027\r", 118 | line: "a ", 119 | }, 120 | { 121 | in: "a b \027\r", 122 | line: "a ", 123 | }, 124 | { 125 | in: "one two thr\x1b[D\027\r", 126 | line: "one two r", 127 | }, 128 | { 129 | in: "\013\r", 130 | line: "", 131 | }, 132 | { 133 | in: "a\013\r", 134 | line: "a", 135 | }, 136 | { 137 | in: "ab\x1b[D\013\r", 138 | line: "a", 139 | }, 140 | } 141 | 142 | func TestKeyPresses(t *testing.T) { 143 | for i, test := range keyPressTests { 144 | for j := 1; j < len(test.in); j++ { 145 | c := &MockTerminal{ 146 | toSend: []byte(test.in), 147 | bytesPerRead: j, 148 | } 149 | ss := NewTerminal(c, "> ") 150 | for k := 0; k < test.throwAwayLines; k++ { 151 | _, err := ss.ReadLine() 152 | if err != nil { 153 | t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err) 154 | } 155 | } 156 | line, err := ss.ReadLine() 157 | if line != test.line { 158 | t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line) 159 | break 160 | } 161 | if err != test.err { 162 | t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err) 163 | break 164 | } 165 | } 166 | } 167 | } 168 | 169 | func TestPasswordNotSaved(t *testing.T) { 170 | c := &MockTerminal{ 171 | toSend: []byte("password\r\x1b[A\r"), 172 | bytesPerRead: 1, 173 | } 174 | ss := NewTerminal(c, "> ") 175 | pw, _ := ss.ReadPassword("> ") 176 | if pw != "password" { 177 | t.Fatalf("failed to read password, got %s", pw) 178 | } 179 | line, _ := ss.ReadLine() 180 | if len(line) > 0 { 181 | t.Fatalf("password was saved in history") 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/terminal/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux,!appengine darwin 6 | 7 | // Package terminal provides support functions for dealing with terminals, as 8 | // commonly found on UNIX systems. 9 | // 10 | // Putting a terminal into raw mode is the most common requirement: 11 | // 12 | // oldState, err := terminal.MakeRaw(0) 13 | // if err != nil { 14 | // panic(err) 15 | // } 16 | // defer terminal.Restore(0, oldState) 17 | package terminal 18 | 19 | import ( 20 | "io" 21 | "syscall" 22 | "unsafe" 23 | ) 24 | 25 | // State contains the state of a terminal. 26 | type State struct { 27 | termios syscall.Termios 28 | } 29 | 30 | // IsTerminal returns true if the given file descriptor is a terminal. 31 | func IsTerminal(fd int) bool { 32 | var termios syscall.Termios 33 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 34 | return err == 0 35 | } 36 | 37 | // MakeRaw put the terminal connected to the given file descriptor into raw 38 | // mode and returns the previous state of the terminal so that it can be 39 | // restored. 40 | func MakeRaw(fd int) (*State, error) { 41 | var oldState State 42 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { 43 | return nil, err 44 | } 45 | 46 | newState := oldState.termios 47 | newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF 48 | newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG 49 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 50 | return nil, err 51 | } 52 | 53 | return &oldState, nil 54 | } 55 | 56 | // GetState returns the current state of a terminal which may be useful to 57 | // restore the terminal after a signal. 58 | func GetState(fd int) (*State, error) { 59 | var oldState State 60 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { 61 | return nil, err 62 | } 63 | 64 | return &oldState, nil 65 | } 66 | 67 | // Restore restores the terminal connected to the given file descriptor to a 68 | // previous state. 69 | func Restore(fd int, state *State) error { 70 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) 71 | return err 72 | } 73 | 74 | // GetSize returns the dimensions of the given terminal. 75 | func GetSize(fd int) (width, height int, err error) { 76 | var dimensions [4]uint16 77 | 78 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 { 79 | return -1, -1, err 80 | } 81 | return int(dimensions[1]), int(dimensions[0]), nil 82 | } 83 | 84 | // ReadPassword reads a line of input from a terminal without local echo. This 85 | // is commonly used for inputting passwords and other sensitive data. The slice 86 | // returned does not include the \n. 87 | func ReadPassword(fd int) ([]byte, error) { 88 | var oldState syscall.Termios 89 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { 90 | return nil, err 91 | } 92 | 93 | newState := oldState 94 | newState.Lflag &^= syscall.ECHO 95 | newState.Lflag |= syscall.ICANON | syscall.ISIG 96 | newState.Iflag |= syscall.ICRNL 97 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 98 | return nil, err 99 | } 100 | 101 | defer func() { 102 | syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) 103 | }() 104 | 105 | var buf [16]byte 106 | var ret []byte 107 | for { 108 | n, err := syscall.Read(fd, buf[:]) 109 | if err != nil { 110 | return nil, err 111 | } 112 | if n == 0 { 113 | if len(ret) == 0 { 114 | return nil, io.EOF 115 | } 116 | break 117 | } 118 | if buf[n-1] == '\n' { 119 | n-- 120 | } 121 | ret = append(ret, buf[:n]...) 122 | if n < len(buf) { 123 | break 124 | } 125 | } 126 | 127 | return ret, nil 128 | } 129 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/terminal/util_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin 6 | 7 | package terminal 8 | 9 | import "syscall" 10 | 11 | const ioctlReadTermios = syscall.TIOCGETA 12 | const ioctlWriteTermios = syscall.TIOCSETA 13 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/terminal/util_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux 6 | 7 | package terminal 8 | 9 | import "syscall" 10 | 11 | const ioctlReadTermios = syscall.TCGETS 12 | const ioctlWriteTermios = syscall.TCSETS 13 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/test/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This package contains integration tests for the 6 | // code.google.com/p/go.crypto/ssh package. 7 | package test 8 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/test/forward_unix_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin freebsd linux netbsd openbsd 6 | 7 | package test 8 | 9 | import ( 10 | "bytes" 11 | "io" 12 | "io/ioutil" 13 | "math/rand" 14 | "net" 15 | "testing" 16 | "time" 17 | ) 18 | 19 | func TestPortForward(t *testing.T) { 20 | server := newServer(t) 21 | defer server.Shutdown() 22 | conn := server.Dial(clientConfig()) 23 | defer conn.Close() 24 | 25 | sshListener, err := conn.Listen("tcp", "localhost:0") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | go func() { 31 | sshConn, err := sshListener.Accept() 32 | if err != nil { 33 | t.Fatalf("listen.Accept failed: %v", err) 34 | } 35 | 36 | _, err = io.Copy(sshConn, sshConn) 37 | if err != nil && err != io.EOF { 38 | t.Fatalf("ssh client copy: %v", err) 39 | } 40 | sshConn.Close() 41 | }() 42 | 43 | forwardedAddr := sshListener.Addr().String() 44 | tcpConn, err := net.Dial("tcp", forwardedAddr) 45 | if err != nil { 46 | t.Fatalf("TCP dial failed: %v", err) 47 | } 48 | 49 | readChan := make(chan []byte) 50 | go func() { 51 | data, _ := ioutil.ReadAll(tcpConn) 52 | readChan <- data 53 | }() 54 | 55 | // Invent some data. 56 | data := make([]byte, 100*1000) 57 | for i := range data { 58 | data[i] = byte(i % 255) 59 | } 60 | 61 | var sent []byte 62 | for len(sent) < 1000*1000 { 63 | // Send random sized chunks 64 | m := rand.Intn(len(data)) 65 | n, err := tcpConn.Write(data[:m]) 66 | if err != nil { 67 | break 68 | } 69 | sent = append(sent, data[:n]...) 70 | } 71 | if err := tcpConn.(*net.TCPConn).CloseWrite(); err != nil { 72 | t.Errorf("tcpConn.CloseWrite: %v", err) 73 | } 74 | 75 | read := <-readChan 76 | 77 | if len(sent) != len(read) { 78 | t.Fatalf("got %d bytes, want %d", len(read), len(sent)) 79 | } 80 | if bytes.Compare(sent, read) != 0 { 81 | t.Fatalf("read back data does not match") 82 | } 83 | 84 | if err := sshListener.Close(); err != nil { 85 | t.Fatalf("sshListener.Close: %v", err) 86 | } 87 | 88 | // Check that the forward disappeared. 89 | tcpConn, err = net.Dial("tcp", forwardedAddr) 90 | if err == nil { 91 | tcpConn.Close() 92 | t.Errorf("still listening to %s after closing", forwardedAddr) 93 | } 94 | } 95 | 96 | func TestAcceptClose(t *testing.T) { 97 | server := newServer(t) 98 | defer server.Shutdown() 99 | conn := server.Dial(clientConfig()) 100 | 101 | sshListener, err := conn.Listen("tcp", "localhost:0") 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | 106 | quit := make(chan error, 1) 107 | go func() { 108 | for { 109 | c, err := sshListener.Accept() 110 | if err != nil { 111 | quit <- err 112 | break 113 | } 114 | c.Close() 115 | } 116 | }() 117 | sshListener.Close() 118 | 119 | select { 120 | case <-time.After(1 * time.Second): 121 | t.Errorf("timeout: listener did not close.") 122 | case err := <-quit: 123 | t.Logf("quit as expected (error %v)", err) 124 | } 125 | } 126 | 127 | // Check that listeners exit if the underlying client transport dies. 128 | func TestPortForwardConnectionClose(t *testing.T) { 129 | server := newServer(t) 130 | defer server.Shutdown() 131 | conn := server.Dial(clientConfig()) 132 | 133 | sshListener, err := conn.Listen("tcp", "localhost:0") 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | 138 | quit := make(chan error, 1) 139 | go func() { 140 | for { 141 | c, err := sshListener.Accept() 142 | if err != nil { 143 | quit <- err 144 | break 145 | } 146 | c.Close() 147 | } 148 | }() 149 | 150 | // It would be even nicer if we closed the server side, but it 151 | // is more involved as the fd for that side is dup()ed. 152 | server.clientConn.Close() 153 | 154 | select { 155 | case <-time.After(1 * time.Second): 156 | t.Errorf("timeout: listener did not close.") 157 | case err := <-quit: 158 | t.Logf("quit as expected (error %v)", err) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/test/keys_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | 11 | "code.google.com/p/go.crypto/ssh" 12 | ) 13 | 14 | var ( 15 | validKey = `AAAAB3NzaC1yc2EAAAADAQABAAABAQDEX/dPu4PmtvgK3La9zioCEDrJ` + 16 | `yUr6xEIK7Pr+rLgydcqWTU/kt7w7gKjOw4vvzgHfjKl09CWyvgb+y5dCiTk` + 17 | `9MxI+erGNhs3pwaoS+EavAbawB7iEqYyTep3YaJK+4RJ4OX7ZlXMAIMrTL+` + 18 | `UVrK89t56hCkFYaAgo3VY+z6rb/b3bDBYtE1Y2tS7C3au73aDgeb9psIrSV` + 19 | `86ucKBTl5X62FnYiyGd++xCnLB6uLximM5OKXfLzJQNS/QyZyk12g3D8y69` + 20 | `Xw1GzCSKX1u1+MQboyf0HJcG2ryUCLHdcDVppApyHx2OLq53hlkQ/yxdflD` + 21 | `qCqAE4j+doagSsIfC1T2T` 22 | 23 | authWithOptions = []string{ 24 | `# comments to ignore before any keys...`, 25 | ``, 26 | `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + validKey + ` user@host`, 27 | `# comments to ignore, along with a blank line`, 28 | ``, 29 | `env="HOME=/home/root2" ssh-rsa ` + validKey + ` user2@host2`, 30 | ``, 31 | `# more comments, plus a invalid entry`, 32 | `ssh-rsa data-that-will-not-parse user@host3`, 33 | } 34 | 35 | authOptions = strings.Join(authWithOptions, "\n") 36 | authWithCRLF = strings.Join(authWithOptions, "\r\n") 37 | authInvalid = []byte(`ssh-rsa`) 38 | authWithQuotedCommaInEnv = []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + validKey + ` user@host`) 39 | authWithQuotedSpaceInEnv = []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + validKey + ` user@host`) 40 | authWithQuotedQuoteInEnv = []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + validKey + ` user@host`) 41 | 42 | authWithDoubleQuotedQuote = []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + validKey + "\t" + `user@host`) 43 | authWithInvalidSpace = []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + validKey + ` user@host 44 | #more to follow but still no valid keys`) 45 | authWithMissingQuote = []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + validKey + ` user@host 46 | env="HOME=/home/root",shared-control ssh-rsa ` + validKey + ` user@host`) 47 | 48 | testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY----- 49 | MIIEowIBAAKCAQEAxF/3T7uD5rb4Cty2vc4qAhA6yclK+sRCCuz6/qy4MnXKlk1P 50 | 5Le8O4CozsOL784B34ypdPQlsr4G/suXQok5PTMSPnqxjYbN6cGqEvhGrwG2sAe4 51 | hKmMk3qd2GiSvuESeDl+2ZVzACDK0y/lFayvPbeeoQpBWGgIKN1WPs+q2/292wwW 52 | LRNWNrUuwt2ru92g4Hm/abCK0lfOrnCgU5eV+thZ2IshnfvsQpyweri8YpjOTil3 53 | y8yUDUv0MmcpNdoNw/MuvV8NRswkil9btfjEG6Mn9ByXBtq8lAix3XA1aaQKch8d 54 | ji6ud4ZZEP8sXX5Q6gqgBOI/naGoErCHwtU9kwIDAQABAoIBAFJRKAp0QEZmTHPB 55 | MZk+4r0asIoFpziXLFgIHu7C2DPOzK1Umzj1DCKlPB3wOqi7Ym2jOSWdcnAK2EPW 56 | dAGgJC5TSkKGjAcXixmB5RkumfKidUI0+lQh/puTurcMnvcEwglDkLkEvMBA/sSo 57 | Pw9m486rOgOnmNzGPyViItURmD2+0yDdLl/vOsO/L1p76GCd0q0J3LqnmsQmawi7 58 | Zwj2Stm6BIrggG5GsF204Iet5219TYLo4g1Qb2AlJ9C8P1FtAWhMwJalDxH9Os2/ 59 | KCDjnaq5n3bXbIU+3QjskjeVXL/Fnbhjnh4zs1EA7eHzl9dCGbcZ2LOimo2PRo8q 60 | wVQmz4ECgYEA9dhiu74TxRVoaO5N2X+FsMzRO8gZdP3Z9IrV4jVN8WT4Vdp0snoF 61 | gkVkqqbQUNKUb5K6B3Js/qNKfcjLbCNq9fewTcT6WsHQdtPbX/QA6Pa2Z29wrlA2 62 | wrIYaAkmVaHny7wsOmgX01aOnuf2MlUnksK43sjZHdIo/m+sDKwwY1cCgYEAzHx4 63 | mwUDMdRF4qpDKJhthraBNejRextNQQYsHVnNaMwZ4aeQcH5l85Cgjm7VpGlbVyBQ 64 | h4zwFvllImp3D2U3mjVkV8Tm9ID98eWvw2YDzBnS3P3SysajD23Z+BXSG9GNv/8k 65 | oAm+bVlvnJy4haK2AcIMk1YFuDuAOmy73abk7iUCgYEAj4qVM1sq/eKfAM1LJRfg 66 | /jbIX+hYfMePD8pUUWygIra6jJ4tjtvSBZrwyPb3IImjY3W/KoP0AcVjxAeORohz 67 | dkP1a6L8LiuFxSuzpdW5BkyuebxGhXCOWKVVvMDC4jLTPVCUXlHSv3GFemCjjgXM 68 | QlNxT5rjsha4Gr8nLIsJAacCgYA4VA1Q/pd7sXKy1p37X8nD8yAyvnh+Be5I/C9I 69 | woUP2jFC9MqYAmmJJ4ziz2swiAkuPeuQ+2Tjnz2ZtmQnrIUdiJmkh8vrDGFnshKx 70 | q7deELsCPzVCwGcIiAUkDra7DQWUHu9y2lxHePyC0rUNst2aLF8UcvzOXC2danhx 71 | vViQtQKBgCmZ7YavE/GNWww8N3xHBJ6UPmUuhQlnAbgNCcdyz30MevBg/JbyUTs2 72 | slftTH15QusJ1UoITnnZuFJ40LqDvh8UhiK09ffM/IbUx839/m2vUOdFZB/WNn9g 73 | Cy0LzddU4KE8JZ/tlk68+hM5fjLLA0aqSunaql5CKfplwLu8x1hL 74 | -----END RSA PRIVATE KEY----- 75 | ` 76 | keys = map[string]string{ 77 | "ssh_host_dsa_key": `-----BEGIN DSA PRIVATE KEY----- 78 | MIIBugIBAAKBgQDe2SIKvZdBp+InawtSXH0NotiMPhm3udyu4hh/E+icMz264kDX 79 | v+sV7ddnSQGQWZ/eVU7Jtx29dCMD1VlFpEd7yGKzmdwJIeA+YquNWoqBRQEJsWWS 80 | 7Fsfvv83dA/DTNIQfOY3+TIs6Mb9vagbgQMU3JUWEhbLE9LCEU6UwwRlpQIVAL4p 81 | JF83SwpE8Jx6KnDpR89npkl/AoGAAy00TdDnAXvStwrZiAFbjZi8xDmPa9WwpfhJ 82 | Rkno45TthDLrS+WmqY8/LTwlqZdOBtoBAynMJfKkUiZM21lWWpL1hRKYdwBlIBy5 83 | XdR2/6wcPSuZ0tCQhDBTstX0Q3P1j198KGKvzy7q9vILKQwtSRqLS1y4JJERafdO 84 | E+9CnGwCgYBz0WwBe2EZtGhGhBdnelTIBeo7PIsr0PzqxQj+dc8PBl8K9FfhRyOp 85 | U39stUvoUxE9vaIFrY1P5xENjLFnPf+hlcuf40GUWEssW9YWPOaBp8afa9hY5Sxs 86 | pvNR6eZFEFOJnx/ZgcA4g+vbrgGi5cM0W470mbGw2CkfJQUafdoIgAIUF+2I9kZe 87 | 2FTBuC9uacqczDlc+0k= 88 | -----END DSA PRIVATE KEY-----`, 89 | "ssh_host_rsa_key": `-----BEGIN RSA PRIVATE KEY----- 90 | MIIEowIBAAKCAQEAuf76Ue2Wtae9oDtaS6rIJgO7iCFTsZUTW9LBsvx/2nli6jKU 91 | d9tUbBRzgdbnRLJ32UljXhERuB/axlrX8/lBzUZ+oYiM0KkEEOXY1z/bcMxdRxGF 92 | XHuf4uXvyC2XyA4+ZvBeS4j1QFyIHZ62o7gAlKMTjiek3B4AQEJAlCLmhH3jB8wc 93 | K/IYXAOlNGM5G44/ZLQpTi8diOV6DLs7tJ7rtEQedOEJfZng5rwp0USFkqcbfDbe 94 | 9/hk0J32jZvOtZNBokYtBb4YEdIiWBzzNtHzU3Dzw61+TKVXaH5HaIvzL9iMrw9f 95 | kJbJyogfZk9BJfemEN+xqP72jlhE8LXNhpTxFQIDAQABAoIBAHbdf+Y5+5XuNF6h 96 | b8xpwW2h9whBnDYiOnP1VfroKWFbMB7R4lZS4joMO+FfkP8zOyqvHwTvza4pFWys 97 | g9SUmDvy8FyVYsC7MzEFYzX0xm3o/Te898ip7P1Zy4rXsGeWysSImwqU5X+TYx3i 98 | 33/zyNM1APtZVJ+jwK9QZ+sD/uPuZK2yS03HGSMZq6ebdoOSaYhluKrxXllSLO1J 99 | KJxDiDdy2lEFw0W8HcI3ly1lg6OI+TRqqaCcLVNF4fNJmYIFM+2VEI9BdgynIh0Q 100 | pMZlJKgaEBcSqCymnTK81ohYD1cV4st2B0km3Sw35Rl04Ij5ITeiya3hp8VfE6UY 101 | PljkA6UCgYEA4811FTFj+kzNZ86C4OW1T5sM4NZt8gcz6CSvVnl+bDzbEOMMyzP7 102 | 2I9zKsR5ApdodH2m8d+RUw1Oe0bNGW5xig/DH/hn9lLQaO52JAi0we8A94dUUMSq 103 | fUk9jKZEXpP/MlfTdJaPos9mxT7z8jREQxIiqH9AV0rLVDOCfDbSWj8CgYEA0QTE 104 | IAUuki3UUqYKzLQrh/QmhY5KTx5amNW9XZ2VGtJvDPJrtBSBZlPEuXZAc4eBWEc7 105 | U3Y9QwsalzupU6Yi6+gmofaXs8xJnj+jKth1DnJvrbLLGlSmf2Ijnwt22TyFUOtt 106 | UAknpjHutDjQPf7pUGWaCPgwwKFsdB8EBjpJF6sCgYAfXesBQAvEK08dPBJJZVfR 107 | 3kenrd71tIgxLtv1zETcIoUHjjv0vvOunhH9kZAYC0EWyTZzl5UrGmn0D4uuNMbt 108 | e74iaNHn2P9Zc3xQ+eHp0j8P1lKFzI6tMaiH9Vz0qOw6wl0bcJ/WizhbcI+migvc 109 | MGMVUHBLlMDqly0gbWwJgQKBgQCgtb9ut01FjANSwORQ3L8Tu3/a9Lrh9n7GQKFn 110 | V4CLrP1BwStavOF5ojMCPo/zxF6JV8ufsqwL3n/FhFP/QyBarpb1tTqTPiHkkR2O 111 | Ffx67TY9IdnUFv4lt3mYEiKBiW0f+MSF42Qe/wmAfKZw5IzUCirTdrFVi0huSGK5 112 | vxrwHQKBgHZ7RoC3I2f6F5fflA2ZAe9oJYC7XT624rY7VeOBwK0W0F47iV3euPi/ 113 | pKvLIBLcWL1Lboo+girnmSZtIYg2iLS3b4T9VFcKWg0y4AVwmhMWe9jWIltfWAAX 114 | 9l0lNikMRGAx3eXudKXEtbGt3/cUzPVaQUHy5LiBxkxnFxgaJPXs 115 | -----END RSA PRIVATE KEY-----`, 116 | "ssh_host_ecdsa_key": `-----BEGIN EC PRIVATE KEY----- 117 | MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49 118 | AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+ 119 | 6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA== 120 | -----END EC PRIVATE KEY-----`, 121 | "authorized_keys": `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEX/dPu4PmtvgK3La9zioCEDrJyUr6xEIK7Pr+rLgydcqWTU/kt7w7gKjOw4vvzgHfjKl09CWyvgb+y5dCiTk9MxI+erGNhs3pwaoS+EavAbawB7iEqYyTep3YaJK+4RJ4OX7ZlXMAIMrTL+UVrK89t56hCkFYaAgo3VY+z6rb/b3bDBYtE1Y2tS7C3au73aDgeb9psIrSV86ucKBTl5X62FnYiyGd++xCnLB6uLximM5OKXfLzJQNS/QyZyk12g3D8y69Xw1GzCSKX1u1+MQboyf0HJcG2ryUCLHdcDVppApyHx2OLq53hlkQ/yxdflDqCqAE4j+doagSsIfC1T2T user@host`, 122 | } 123 | ) 124 | 125 | func TestMarshalParsePublicKey(t *testing.T) { 126 | pub := getTestPublicKey(t) 127 | authKeys := ssh.MarshalAuthorizedKey(pub) 128 | actualFields := strings.Fields(string(authKeys)) 129 | if len(actualFields) == 0 { 130 | t.Fatalf("failed authKeys: %v", authKeys) 131 | } 132 | 133 | // drop the comment 134 | expectedFields := strings.Fields(keys["authorized_keys"])[0:2] 135 | 136 | if !reflect.DeepEqual(actualFields, expectedFields) { 137 | t.Errorf("got %v, expected %v", actualFields, expectedFields) 138 | } 139 | 140 | actPub, _, _, _, ok := ssh.ParseAuthorizedKey([]byte(keys["authorized_keys"])) 141 | if !ok { 142 | t.Fatalf("cannot parse %v", keys["authorized_keys"]) 143 | } 144 | if !reflect.DeepEqual(actPub, pub) { 145 | t.Errorf("got %v, expected %v", actPub, pub) 146 | } 147 | } 148 | 149 | type authResult struct { 150 | pubKey interface{} //*rsa.PublicKey 151 | options []string 152 | comments string 153 | rest string 154 | ok bool 155 | } 156 | 157 | func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) { 158 | rest := authKeys 159 | var values []authResult 160 | for len(rest) > 0 { 161 | var r authResult 162 | r.pubKey, r.comments, r.options, rest, r.ok = ssh.ParseAuthorizedKey(rest) 163 | r.rest = string(rest) 164 | values = append(values, r) 165 | } 166 | 167 | if !reflect.DeepEqual(values, expected) { 168 | t.Errorf("got %q, expected %q", values, expected) 169 | } 170 | 171 | } 172 | 173 | func getTestPublicKey(t *testing.T) *rsa.PublicKey { 174 | block, _ := pem.Decode([]byte(testClientPrivateKey)) 175 | if block == nil { 176 | t.Fatalf("pem.Decode: %v", testClientPrivateKey) 177 | } 178 | priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) 179 | if err != nil { 180 | t.Fatalf("x509.ParsePKCS1PrivateKey: %v", err) 181 | } 182 | 183 | return &priv.PublicKey 184 | } 185 | 186 | func TestAuth(t *testing.T) { 187 | pub := getTestPublicKey(t) 188 | rest2 := strings.Join(authWithOptions[3:], "\n") 189 | rest3 := strings.Join(authWithOptions[6:], "\n") 190 | testAuthorizedKeys(t, []byte(authOptions), []authResult{ 191 | {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, 192 | {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, 193 | {nil, nil, "", "", false}, 194 | }) 195 | } 196 | 197 | func TestAuthWithCRLF(t *testing.T) { 198 | pub := getTestPublicKey(t) 199 | rest2 := strings.Join(authWithOptions[3:], "\r\n") 200 | rest3 := strings.Join(authWithOptions[6:], "\r\n") 201 | testAuthorizedKeys(t, []byte(authWithCRLF), []authResult{ 202 | {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, 203 | {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, 204 | {nil, nil, "", "", false}, 205 | }) 206 | } 207 | 208 | func TestAuthWithQuotedSpaceInEnv(t *testing.T) { 209 | pub := getTestPublicKey(t) 210 | testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{ 211 | {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true}, 212 | }) 213 | } 214 | 215 | func TestAuthWithQuotedCommaInEnv(t *testing.T) { 216 | pub := getTestPublicKey(t) 217 | testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{ 218 | {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true}, 219 | }) 220 | } 221 | 222 | func TestAuthWithQuotedQuoteInEnv(t *testing.T) { 223 | pub := getTestPublicKey(t) 224 | testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{ 225 | {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true}, 226 | }) 227 | 228 | testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{ 229 | {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true}, 230 | }) 231 | 232 | } 233 | 234 | func TestAuthWithInvalidSpace(t *testing.T) { 235 | testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{ 236 | {nil, nil, "", "", false}, 237 | }) 238 | } 239 | 240 | func TestAuthWithMissingQuote(t *testing.T) { 241 | pub := getTestPublicKey(t) 242 | testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{ 243 | {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true}, 244 | }) 245 | } 246 | 247 | func TestInvalidEntry(t *testing.T) { 248 | _, _, _, _, ok := ssh.ParseAuthorizedKey(authInvalid) 249 | if ok { 250 | t.Errorf("Expected invalid entry, returned valid entry") 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/test/session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !windows 6 | 7 | package test 8 | 9 | // Session functional tests. 10 | 11 | import ( 12 | "bytes" 13 | "code.google.com/p/go.crypto/ssh" 14 | "io" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | func TestRunCommandSuccess(t *testing.T) { 20 | server := newServer(t) 21 | defer server.Shutdown() 22 | conn := server.Dial(clientConfig()) 23 | defer conn.Close() 24 | 25 | session, err := conn.NewSession() 26 | if err != nil { 27 | t.Fatalf("session failed: %v", err) 28 | } 29 | defer session.Close() 30 | err = session.Run("true") 31 | if err != nil { 32 | t.Fatalf("session failed: %v", err) 33 | } 34 | } 35 | 36 | func TestHostKeyCheck(t *testing.T) { 37 | server := newServer(t) 38 | defer server.Shutdown() 39 | 40 | conf := clientConfig() 41 | k := conf.HostKeyChecker.(*storedHostKey) 42 | 43 | // change the key. 44 | k.keys["ssh-rsa"][25]++ 45 | 46 | conn, err := server.TryDial(conf) 47 | if err == nil { 48 | conn.Close() 49 | t.Fatalf("dial should have failed.") 50 | } else if !strings.Contains(err.Error(), "host key mismatch") { 51 | t.Fatalf("'host key mismatch' not found in %v", err) 52 | } 53 | } 54 | 55 | func TestRunCommandFailed(t *testing.T) { 56 | server := newServer(t) 57 | defer server.Shutdown() 58 | conn := server.Dial(clientConfig()) 59 | defer conn.Close() 60 | 61 | session, err := conn.NewSession() 62 | if err != nil { 63 | t.Fatalf("session failed: %v", err) 64 | } 65 | defer session.Close() 66 | err = session.Run(`bash -c "kill -9 $$"`) 67 | if err == nil { 68 | t.Fatalf("session succeeded: %v", err) 69 | } 70 | } 71 | 72 | func TestRunCommandWeClosed(t *testing.T) { 73 | server := newServer(t) 74 | defer server.Shutdown() 75 | conn := server.Dial(clientConfig()) 76 | defer conn.Close() 77 | 78 | session, err := conn.NewSession() 79 | if err != nil { 80 | t.Fatalf("session failed: %v", err) 81 | } 82 | err = session.Shell() 83 | if err != nil { 84 | t.Fatalf("shell failed: %v", err) 85 | } 86 | err = session.Close() 87 | if err != nil { 88 | t.Fatalf("shell failed: %v", err) 89 | } 90 | } 91 | 92 | func TestFuncLargeRead(t *testing.T) { 93 | server := newServer(t) 94 | defer server.Shutdown() 95 | conn := server.Dial(clientConfig()) 96 | defer conn.Close() 97 | 98 | session, err := conn.NewSession() 99 | if err != nil { 100 | t.Fatalf("unable to create new session: %s", err) 101 | } 102 | 103 | stdout, err := session.StdoutPipe() 104 | if err != nil { 105 | t.Fatalf("unable to acquire stdout pipe: %s", err) 106 | } 107 | 108 | err = session.Start("dd if=/dev/urandom bs=2048 count=1") 109 | if err != nil { 110 | t.Fatalf("unable to execute remote command: %s", err) 111 | } 112 | 113 | buf := new(bytes.Buffer) 114 | n, err := io.Copy(buf, stdout) 115 | if err != nil { 116 | t.Fatalf("error reading from remote stdout: %s", err) 117 | } 118 | 119 | if n != 2048 { 120 | t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n) 121 | } 122 | } 123 | 124 | func TestInvalidTerminalMode(t *testing.T) { 125 | server := newServer(t) 126 | defer server.Shutdown() 127 | conn := server.Dial(clientConfig()) 128 | defer conn.Close() 129 | 130 | session, err := conn.NewSession() 131 | if err != nil { 132 | t.Fatalf("session failed: %v", err) 133 | } 134 | defer session.Close() 135 | 136 | if err = session.RequestPty("vt100", 80, 40, ssh.TerminalModes{255: 1984}); err == nil { 137 | t.Fatalf("req-pty failed: successful request with invalid mode") 138 | } 139 | } 140 | 141 | func TestValidTerminalMode(t *testing.T) { 142 | server := newServer(t) 143 | defer server.Shutdown() 144 | conn := server.Dial(clientConfig()) 145 | defer conn.Close() 146 | 147 | session, err := conn.NewSession() 148 | if err != nil { 149 | t.Fatalf("session failed: %v", err) 150 | } 151 | defer session.Close() 152 | 153 | stdout, err := session.StdoutPipe() 154 | if err != nil { 155 | t.Fatalf("unable to acquire stdout pipe: %s", err) 156 | } 157 | 158 | stdin, err := session.StdinPipe() 159 | if err != nil { 160 | t.Fatalf("unable to acquire stdin pipe: %s", err) 161 | } 162 | 163 | tm := ssh.TerminalModes{ssh.ECHO: 0} 164 | if err = session.RequestPty("xterm", 80, 40, tm); err != nil { 165 | t.Fatalf("req-pty failed: %s", err) 166 | } 167 | 168 | err = session.Shell() 169 | if err != nil { 170 | t.Fatalf("session failed: %s", err) 171 | } 172 | 173 | stdin.Write([]byte("stty -a && exit\n")) 174 | 175 | var buf bytes.Buffer 176 | if _, err := io.Copy(&buf, stdout); err != nil { 177 | t.Fatalf("reading failed: %s", err) 178 | } 179 | 180 | if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") { 181 | t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/test/tcpip_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !windows 6 | 7 | package test 8 | 9 | // direct-tcpip functional tests 10 | 11 | import ( 12 | "net" 13 | "net/http" 14 | "testing" 15 | ) 16 | 17 | func TestTCPIPHTTP(t *testing.T) { 18 | // google.com will generate at least one redirect, possibly three 19 | // depending on your location. 20 | doTest(t, "http://google.com") 21 | } 22 | 23 | func TestTCPIPHTTPS(t *testing.T) { 24 | doTest(t, "https://encrypted.google.com/") 25 | } 26 | 27 | func doTest(t *testing.T, url string) { 28 | server := newServer(t) 29 | defer server.Shutdown() 30 | conn := server.Dial(clientConfig()) 31 | defer conn.Close() 32 | 33 | tr := &http.Transport{ 34 | Dial: func(n, addr string) (net.Conn, error) { 35 | return conn.Dial(n, addr) 36 | }, 37 | } 38 | client := &http.Client{ 39 | Transport: tr, 40 | } 41 | resp, err := client.Get(url) 42 | if err != nil { 43 | t.Fatalf("unable to proxy: %s", err) 44 | } 45 | // got a body without error 46 | t.Log(resp) 47 | } 48 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/test/test_unix_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin freebsd linux netbsd openbsd 6 | 7 | package test 8 | 9 | // functional test harness for unix. 10 | 11 | import ( 12 | "bytes" 13 | "crypto" 14 | "crypto/dsa" 15 | "crypto/rsa" 16 | "crypto/x509" 17 | "encoding/pem" 18 | "errors" 19 | "io" 20 | "io/ioutil" 21 | "log" 22 | "net" 23 | "os" 24 | "os/exec" 25 | "os/user" 26 | "path/filepath" 27 | "testing" 28 | "text/template" 29 | 30 | "code.google.com/p/go.crypto/ssh" 31 | ) 32 | 33 | const sshd_config = ` 34 | Protocol 2 35 | HostKey {{.Dir}}/ssh_host_rsa_key 36 | HostKey {{.Dir}}/ssh_host_dsa_key 37 | HostKey {{.Dir}}/ssh_host_ecdsa_key 38 | Pidfile {{.Dir}}/sshd.pid 39 | #UsePrivilegeSeparation no 40 | KeyRegenerationInterval 3600 41 | ServerKeyBits 768 42 | SyslogFacility AUTH 43 | LogLevel DEBUG2 44 | LoginGraceTime 120 45 | PermitRootLogin no 46 | StrictModes no 47 | RSAAuthentication yes 48 | PubkeyAuthentication yes 49 | AuthorizedKeysFile {{.Dir}}/authorized_keys 50 | IgnoreRhosts yes 51 | RhostsRSAAuthentication no 52 | HostbasedAuthentication no 53 | ` 54 | 55 | var ( 56 | configTmpl template.Template 57 | rsakey *rsa.PrivateKey 58 | serializedHostKey []byte 59 | ) 60 | 61 | func init() { 62 | template.Must(configTmpl.Parse(sshd_config)) 63 | block, _ := pem.Decode([]byte(testClientPrivateKey)) 64 | rsakey, _ = x509.ParsePKCS1PrivateKey(block.Bytes) 65 | 66 | block, _ = pem.Decode([]byte(keys["ssh_host_rsa_key"])) 67 | if block == nil { 68 | panic("pem.Decode ssh_host_rsa_key") 69 | } 70 | priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) 71 | if err != nil { 72 | panic("ParsePKCS1PrivateKey: " + err.Error()) 73 | } 74 | serializedHostKey = ssh.MarshalPublicKey(&priv.PublicKey) 75 | } 76 | 77 | type server struct { 78 | t *testing.T 79 | cleanup func() // executed during Shutdown 80 | configfile string 81 | cmd *exec.Cmd 82 | output bytes.Buffer // holds stderr from sshd process 83 | 84 | // Client half of the network connection. 85 | clientConn net.Conn 86 | } 87 | 88 | func username() string { 89 | var username string 90 | if user, err := user.Current(); err == nil { 91 | username = user.Username 92 | } else { 93 | // user.Current() currently requires cgo. If an error is 94 | // returned attempt to get the username from the environment. 95 | log.Printf("user.Current: %v; falling back on $USER", err) 96 | username = os.Getenv("USER") 97 | } 98 | if username == "" { 99 | panic("Unable to get username") 100 | } 101 | return username 102 | } 103 | 104 | type storedHostKey struct { 105 | // keys map from an algorithm string to binary key data. 106 | keys map[string][]byte 107 | } 108 | 109 | func (k *storedHostKey) Add(algo string, public []byte) { 110 | if k.keys == nil { 111 | k.keys = map[string][]byte{} 112 | } 113 | k.keys[algo] = append([]byte(nil), public...) 114 | } 115 | 116 | func (k *storedHostKey) Check(addr string, remote net.Addr, algo string, key []byte) error { 117 | if k.keys == nil || bytes.Compare(key, k.keys[algo]) != 0 { 118 | return errors.New("host key mismatch") 119 | } 120 | return nil 121 | } 122 | 123 | func clientConfig() *ssh.ClientConfig { 124 | keyChecker := storedHostKey{} 125 | keyChecker.Add("ssh-rsa", serializedHostKey) 126 | 127 | kc := new(keychain) 128 | kc.keys = append(kc.keys, rsakey) 129 | config := &ssh.ClientConfig{ 130 | User: username(), 131 | Auth: []ssh.ClientAuth{ 132 | ssh.ClientAuthKeyring(kc), 133 | }, 134 | HostKeyChecker: &keyChecker, 135 | } 136 | return config 137 | } 138 | 139 | // unixConnection creates two halves of a connected net.UnixConn. It 140 | // is used for connecting the Go SSH client with sshd without opening 141 | // ports. 142 | func unixConnection() (*net.UnixConn, *net.UnixConn, error) { 143 | dir, err := ioutil.TempDir("", "unixConnection") 144 | if err != nil { 145 | return nil, nil, err 146 | } 147 | defer os.Remove(dir) 148 | 149 | addr := filepath.Join(dir, "ssh") 150 | listener, err := net.Listen("unix", addr) 151 | if err != nil { 152 | return nil, nil, err 153 | } 154 | defer listener.Close() 155 | c1, err := net.Dial("unix", addr) 156 | if err != nil { 157 | return nil, nil, err 158 | } 159 | 160 | c2, err := listener.Accept() 161 | if err != nil { 162 | c1.Close() 163 | return nil, nil, err 164 | } 165 | 166 | return c1.(*net.UnixConn), c2.(*net.UnixConn), nil 167 | } 168 | 169 | func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.ClientConn, error) { 170 | sshd, err := exec.LookPath("sshd") 171 | if err != nil { 172 | s.t.Skipf("skipping test: %v", err) 173 | } 174 | 175 | c1, c2, err := unixConnection() 176 | if err != nil { 177 | s.t.Fatalf("unixConnection: %v", err) 178 | } 179 | 180 | s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e") 181 | f, err := c2.File() 182 | if err != nil { 183 | s.t.Fatalf("UnixConn.File: %v", err) 184 | } 185 | defer f.Close() 186 | s.cmd.Stdin = f 187 | s.cmd.Stdout = f 188 | s.cmd.Stderr = &s.output 189 | if err := s.cmd.Start(); err != nil { 190 | s.t.Fail() 191 | s.Shutdown() 192 | s.t.Fatalf("s.cmd.Start: %v", err) 193 | } 194 | s.clientConn = c1 195 | return ssh.Client(c1, config) 196 | } 197 | 198 | func (s *server) Dial(config *ssh.ClientConfig) *ssh.ClientConn { 199 | conn, err := s.TryDial(config) 200 | if err != nil { 201 | s.t.Fail() 202 | s.Shutdown() 203 | s.t.Fatalf("ssh.Client: %v", err) 204 | } 205 | return conn 206 | } 207 | 208 | func (s *server) Shutdown() { 209 | if s.cmd != nil && s.cmd.Process != nil { 210 | // Don't check for errors; if it fails it's most 211 | // likely "os: process already finished", and we don't 212 | // care about that. Use os.Interrupt, so child 213 | // processes are killed too. 214 | s.cmd.Process.Signal(os.Interrupt) 215 | s.cmd.Wait() 216 | } 217 | if s.t.Failed() { 218 | // log any output from sshd process 219 | s.t.Logf("sshd: %s", s.output.String()) 220 | } 221 | s.cleanup() 222 | } 223 | 224 | // newServer returns a new mock ssh server. 225 | func newServer(t *testing.T) *server { 226 | dir, err := ioutil.TempDir("", "sshtest") 227 | if err != nil { 228 | t.Fatal(err) 229 | } 230 | f, err := os.Create(filepath.Join(dir, "sshd_config")) 231 | if err != nil { 232 | t.Fatal(err) 233 | } 234 | err = configTmpl.Execute(f, map[string]string{ 235 | "Dir": dir, 236 | }) 237 | if err != nil { 238 | t.Fatal(err) 239 | } 240 | f.Close() 241 | 242 | for k, v := range keys { 243 | f, err := os.OpenFile(filepath.Join(dir, k), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) 244 | if err != nil { 245 | t.Fatal(err) 246 | } 247 | if _, err := f.Write([]byte(v)); err != nil { 248 | t.Fatal(err) 249 | } 250 | f.Close() 251 | } 252 | 253 | return &server{ 254 | t: t, 255 | configfile: f.Name(), 256 | cleanup: func() { 257 | if err := os.RemoveAll(dir); err != nil { 258 | t.Error(err) 259 | } 260 | }, 261 | } 262 | } 263 | 264 | // keychain implements the ClientKeyring interface 265 | type keychain struct { 266 | keys []interface{} 267 | } 268 | 269 | func (k *keychain) Key(i int) (interface{}, error) { 270 | if i < 0 || i >= len(k.keys) { 271 | return nil, nil 272 | } 273 | switch key := k.keys[i].(type) { 274 | case *rsa.PrivateKey: 275 | return &key.PublicKey, nil 276 | case *dsa.PrivateKey: 277 | return &key.PublicKey, nil 278 | } 279 | panic("unknown key type") 280 | } 281 | 282 | func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) { 283 | hashFunc := crypto.SHA1 284 | h := hashFunc.New() 285 | h.Write(data) 286 | digest := h.Sum(nil) 287 | switch key := k.keys[i].(type) { 288 | case *rsa.PrivateKey: 289 | return rsa.SignPKCS1v15(rand, key, hashFunc, digest) 290 | } 291 | return nil, errors.New("ssh: unknown key type") 292 | } 293 | 294 | func (k *keychain) loadPEM(file string) error { 295 | buf, err := ioutil.ReadFile(file) 296 | if err != nil { 297 | return err 298 | } 299 | block, _ := pem.Decode(buf) 300 | if block == nil { 301 | return errors.New("ssh: no key found") 302 | } 303 | r, err := x509.ParsePKCS1PrivateKey(block.Bytes) 304 | if err != nil { 305 | return err 306 | } 307 | k.keys = append(k.keys, r) 308 | return nil 309 | } 310 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "bufio" 9 | "crypto" 10 | "crypto/cipher" 11 | "crypto/subtle" 12 | "encoding/binary" 13 | "errors" 14 | "hash" 15 | "io" 16 | "net" 17 | "sync" 18 | ) 19 | 20 | const ( 21 | packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. 22 | 23 | // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations 24 | // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC 25 | // indicates implementations SHOULD be able to handle larger packet sizes, but then 26 | // waffles on about reasonable limits. 27 | // 28 | // OpenSSH caps their maxPacket at 256kb so we choose to do the same. 29 | maxPacket = 256 * 1024 30 | ) 31 | 32 | // conn represents an ssh transport that implements packet based 33 | // operations. 34 | type conn interface { 35 | // Encrypt and send a packet of data to the remote peer. 36 | writePacket(packet []byte) error 37 | 38 | // Close closes the connection. 39 | Close() error 40 | } 41 | 42 | // transport represents the SSH connection to the remote peer. 43 | type transport struct { 44 | reader 45 | writer 46 | 47 | net.Conn 48 | } 49 | 50 | // reader represents the incoming connection state. 51 | type reader struct { 52 | io.Reader 53 | common 54 | } 55 | 56 | // writer represents the outgoing connection state. 57 | type writer struct { 58 | sync.Mutex // protects writer.Writer from concurrent writes 59 | *bufio.Writer 60 | rand io.Reader 61 | common 62 | } 63 | 64 | // common represents the cipher state needed to process messages in a single 65 | // direction. 66 | type common struct { 67 | seqNum uint32 68 | mac hash.Hash 69 | cipher cipher.Stream 70 | 71 | cipherAlgo string 72 | macAlgo string 73 | compressionAlgo string 74 | } 75 | 76 | // Read and decrypt a single packet from the remote peer. 77 | func (r *reader) readOnePacket() ([]byte, error) { 78 | var lengthBytes = make([]byte, 5) 79 | var macSize uint32 80 | if _, err := io.ReadFull(r, lengthBytes); err != nil { 81 | return nil, err 82 | } 83 | 84 | r.cipher.XORKeyStream(lengthBytes, lengthBytes) 85 | 86 | if r.mac != nil { 87 | r.mac.Reset() 88 | seqNumBytes := []byte{ 89 | byte(r.seqNum >> 24), 90 | byte(r.seqNum >> 16), 91 | byte(r.seqNum >> 8), 92 | byte(r.seqNum), 93 | } 94 | r.mac.Write(seqNumBytes) 95 | r.mac.Write(lengthBytes) 96 | macSize = uint32(r.mac.Size()) 97 | } 98 | 99 | length := binary.BigEndian.Uint32(lengthBytes[0:4]) 100 | paddingLength := uint32(lengthBytes[4]) 101 | 102 | if length <= paddingLength+1 { 103 | return nil, errors.New("ssh: invalid packet length, packet too small") 104 | } 105 | 106 | if length > maxPacket { 107 | return nil, errors.New("ssh: invalid packet length, packet too large") 108 | } 109 | 110 | packet := make([]byte, length-1+macSize) 111 | if _, err := io.ReadFull(r, packet); err != nil { 112 | return nil, err 113 | } 114 | mac := packet[length-1:] 115 | r.cipher.XORKeyStream(packet, packet[:length-1]) 116 | 117 | if r.mac != nil { 118 | r.mac.Write(packet[:length-1]) 119 | if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 { 120 | return nil, errors.New("ssh: MAC failure") 121 | } 122 | } 123 | 124 | r.seqNum++ 125 | return packet[:length-paddingLength-1], nil 126 | } 127 | 128 | // Read and decrypt next packet discarding debug and noop messages. 129 | func (t *transport) readPacket() ([]byte, error) { 130 | for { 131 | packet, err := t.readOnePacket() 132 | if err != nil { 133 | return nil, err 134 | } 135 | if len(packet) == 0 { 136 | return nil, errors.New("ssh: zero length packet") 137 | } 138 | if packet[0] != msgIgnore && packet[0] != msgDebug { 139 | return packet, nil 140 | } 141 | } 142 | panic("unreachable") 143 | } 144 | 145 | // Encrypt and send a packet of data to the remote peer. 146 | func (w *writer) writePacket(packet []byte) error { 147 | if len(packet) > maxPacket { 148 | return errors.New("ssh: packet too large") 149 | } 150 | w.Mutex.Lock() 151 | defer w.Mutex.Unlock() 152 | 153 | paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple 154 | if paddingLength < 4 { 155 | paddingLength += packetSizeMultiple 156 | } 157 | 158 | length := len(packet) + 1 + paddingLength 159 | lengthBytes := []byte{ 160 | byte(length >> 24), 161 | byte(length >> 16), 162 | byte(length >> 8), 163 | byte(length), 164 | byte(paddingLength), 165 | } 166 | padding := make([]byte, paddingLength) 167 | _, err := io.ReadFull(w.rand, padding) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | if w.mac != nil { 173 | w.mac.Reset() 174 | seqNumBytes := []byte{ 175 | byte(w.seqNum >> 24), 176 | byte(w.seqNum >> 16), 177 | byte(w.seqNum >> 8), 178 | byte(w.seqNum), 179 | } 180 | w.mac.Write(seqNumBytes) 181 | w.mac.Write(lengthBytes) 182 | w.mac.Write(packet) 183 | w.mac.Write(padding) 184 | } 185 | 186 | // TODO(dfc) lengthBytes, packet and padding should be 187 | // subslices of a single buffer 188 | w.cipher.XORKeyStream(lengthBytes, lengthBytes) 189 | w.cipher.XORKeyStream(packet, packet) 190 | w.cipher.XORKeyStream(padding, padding) 191 | 192 | if _, err := w.Write(lengthBytes); err != nil { 193 | return err 194 | } 195 | if _, err := w.Write(packet); err != nil { 196 | return err 197 | } 198 | if _, err := w.Write(padding); err != nil { 199 | return err 200 | } 201 | 202 | if w.mac != nil { 203 | if _, err := w.Write(w.mac.Sum(nil)); err != nil { 204 | return err 205 | } 206 | } 207 | 208 | w.seqNum++ 209 | return w.Flush() 210 | } 211 | 212 | func newTransport(conn net.Conn, rand io.Reader) *transport { 213 | return &transport{ 214 | reader: reader{ 215 | Reader: bufio.NewReader(conn), 216 | common: common{ 217 | cipher: noneCipher{}, 218 | }, 219 | }, 220 | writer: writer{ 221 | Writer: bufio.NewWriter(conn), 222 | rand: rand, 223 | common: common{ 224 | cipher: noneCipher{}, 225 | }, 226 | }, 227 | Conn: conn, 228 | } 229 | } 230 | 231 | type direction struct { 232 | ivTag []byte 233 | keyTag []byte 234 | macKeyTag []byte 235 | } 236 | 237 | // TODO(dfc) can this be made a constant ? 238 | var ( 239 | serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} 240 | clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} 241 | ) 242 | 243 | // setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as 244 | // described in RFC 4253, section 6.4. direction should either be serverKeys 245 | // (to setup server->client keys) or clientKeys (for client->server keys). 246 | func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error { 247 | cipherMode := cipherModes[c.cipherAlgo] 248 | macMode := macModes[c.macAlgo] 249 | 250 | iv := make([]byte, cipherMode.ivSize) 251 | key := make([]byte, cipherMode.keySize) 252 | macKey := make([]byte, macMode.keySize) 253 | 254 | h := hashFunc.New() 255 | generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h) 256 | generateKeyMaterial(key, d.keyTag, K, H, sessionId, h) 257 | generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h) 258 | 259 | c.mac = macMode.new(macKey) 260 | 261 | var err error 262 | c.cipher, err = cipherMode.createCipher(key, iv) 263 | return err 264 | } 265 | 266 | // generateKeyMaterial fills out with key material generated from tag, K, H 267 | // and sessionId, as specified in RFC 4253, section 7.2. 268 | func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) { 269 | var digestsSoFar []byte 270 | 271 | for len(out) > 0 { 272 | h.Reset() 273 | h.Write(K) 274 | h.Write(H) 275 | 276 | if len(digestsSoFar) == 0 { 277 | h.Write(tag) 278 | h.Write(sessionId) 279 | } else { 280 | h.Write(digestsSoFar) 281 | } 282 | 283 | digest := h.Sum(nil) 284 | n := copy(out, digest) 285 | out = out[n:] 286 | if len(out) > 0 { 287 | digestsSoFar = append(digestsSoFar, digest...) 288 | } 289 | } 290 | } 291 | 292 | // maxVersionStringBytes is the maximum number of bytes that we'll accept as a 293 | // version string. In the event that the client is talking a different protocol 294 | // we need to set a limit otherwise we will keep using more and more memory 295 | // while searching for the end of the version handshake. 296 | const maxVersionStringBytes = 1024 297 | 298 | // Read version string as specified by RFC 4253, section 4.2. 299 | func readVersion(r io.Reader) ([]byte, error) { 300 | versionString := make([]byte, 0, 64) 301 | var ok bool 302 | var buf [1]byte 303 | forEachByte: 304 | for len(versionString) < maxVersionStringBytes { 305 | _, err := io.ReadFull(r, buf[:]) 306 | if err != nil { 307 | return nil, err 308 | } 309 | // The RFC says that the version should be terminated with \r\n 310 | // but several SSH servers actually only send a \n. 311 | if buf[0] == '\n' { 312 | ok = true 313 | break forEachByte 314 | } 315 | versionString = append(versionString, buf[0]) 316 | } 317 | 318 | if !ok { 319 | return nil, errors.New("ssh: failed to read version string") 320 | } 321 | 322 | // There might be a '\r' on the end which we should remove. 323 | if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { 324 | versionString = versionString[:len(versionString)-1] 325 | } 326 | return versionString, nil 327 | } 328 | -------------------------------------------------------------------------------- /src/code.google.com/p/go.crypto/ssh/transport_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ssh 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "testing" 11 | ) 12 | 13 | func TestReadVersion(t *testing.T) { 14 | buf := serverVersion 15 | result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))) 16 | if err != nil { 17 | t.Errorf("readVersion didn't read version correctly: %s", err) 18 | } 19 | if !bytes.Equal(buf[:len(buf)-2], result) { 20 | t.Error("version read did not match expected") 21 | } 22 | } 23 | 24 | func TestReadVersionWithJustLF(t *testing.T) { 25 | var buf []byte 26 | buf = append(buf, serverVersion...) 27 | buf = buf[:len(buf)-1] 28 | buf[len(buf)-1] = '\n' 29 | result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))) 30 | if err != nil { 31 | t.Error("readVersion failed to handle just a \n") 32 | } 33 | if !bytes.Equal(buf[:len(buf)-1], result) { 34 | t.Errorf("version read did not match expected: got %x, want %x", result, buf[:len(buf)-1]) 35 | } 36 | } 37 | 38 | func TestReadVersionTooLong(t *testing.T) { 39 | buf := make([]byte, maxVersionStringBytes+1) 40 | if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil { 41 | t.Errorf("readVersion consumed %d bytes without error", len(buf)) 42 | } 43 | } 44 | 45 | func TestReadVersionWithoutCRLF(t *testing.T) { 46 | buf := serverVersion 47 | buf = buf[:len(buf)-1] 48 | if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil { 49 | t.Error("readVersion did not notice \\n was missing") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | //"fmt" 6 | "github.com/robfig/config" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type ConfigInfo struct { 16 | BusinessList string 17 | MasterConf string 18 | MasterRestartScript string 19 | CopyMethod int 20 | SlaveServerIp string 21 | SlaveServerSSHPort int 22 | SlaveRemoteUser string 23 | SlaveRemotePasswd string 24 | SSHCommandPath string 25 | ScpCommandPath string 26 | SlaveConf string 27 | SlaveRestartScript string 28 | StoreScheme int 29 | DBDriverName string 30 | DBDataSourceName string 31 | FileToReplaceDB string 32 | MasterStatsPage string 33 | SlaveStatsPage string 34 | Vip string 35 | NewHAProxyConfPath string 36 | } 37 | 38 | /* 39 | // 函数checkBusinessList使用 40 | func quickSort(portRangeList [][2]int, left int, right int) { 41 | temp := portRangeList[left] 42 | p := left 43 | i, j := left, right 44 | 45 | for i <= j { 46 | for j >= p && portRangeList[j][0] >= temp[0] { 47 | j-- 48 | } 49 | if j >= p { 50 | portRangeList[p] = portRangeList[j] 51 | p = j 52 | } 53 | 54 | if portRangeList[i][0] <= temp[0] && i <= p { 55 | i++ 56 | } 57 | 58 | if i <= p { 59 | portRangeList[p] = portRangeList[i] 60 | p = i 61 | } 62 | } 63 | portRangeList[p] = temp 64 | if p - left > 1 { 65 | quickSort(portRangeList, left, p - 1) 66 | } 67 | if right - p > 1 { 68 | quickSort(portRangeList, p + 1, right) 69 | } 70 | } 71 | */ 72 | 73 | type portRanges [][2]int 74 | 75 | func (prs portRanges) Len() int { 76 | return len(prs) 77 | } 78 | 79 | func (prs portRanges) Swap(i, j int) { 80 | prs[i], prs[j] = prs[j], prs[i] 81 | } 82 | 83 | func (prs portRanges) Less(i, j int) bool { 84 | return prs[i][0] <= prs[j][0] 85 | } 86 | 87 | // 检查业务列表配置项是否正确 88 | func checkBusinessList(bl string) (err error) { 89 | 90 | if bl == "" { 91 | return 92 | } 93 | 94 | // 允许使用1000-99999范围内的端口 95 | matched, _ := regexp.MatchString(`^(.+,\d{4,5}-\d{4,5};)*(.+,\d{4,5}-\d{4,5})$`, bl) 96 | if matched == false { 97 | err = errors.New("启动失败:业务端口区间列表BusinessList配置的值有误!请检查!") 98 | return 99 | } 100 | // 检查端口范围的开始值是否大于结束值,以及业务端口范围是否有重叠 101 | // 预估业务类型数目不超过15个 102 | businesses := strings.Split(bl, ";") 103 | nameToPortRanges := make(map[string][2]int) 104 | portRangeList := make([][2]int, 0, 15) 105 | for _, business := range businesses { 106 | nameToPortRange := strings.Split(business, ",") 107 | portRange := strings.Split(nameToPortRange[1], "-") 108 | beginPort, _ := strconv.Atoi(portRange[0]) 109 | endPort, _ := strconv.Atoi(portRange[1]) 110 | nameToPortRanges[nameToPortRange[0]] = [2]int{beginPort, endPort} 111 | portRangeList = append(portRangeList, [2]int{beginPort, endPort}) 112 | } 113 | 114 | // 端口范围的开始值是否大于结束值 115 | beginGtEnd := false 116 | beginGtEndBusiness := make([]string, 0, 15) 117 | for businessName, portRange := range nameToPortRanges { 118 | if portRange[0] > portRange[1] || portRange[0] < 1000 || portRange[1] > 99999 { 119 | beginGtEnd = true 120 | beginGtEndBusiness = append(beginGtEndBusiness, businessName) 121 | } 122 | } 123 | if beginGtEnd { 124 | err = errors.New("启动失败:"+strings.Join(beginGtEndBusiness, ",")+"的端口范围有误!") 125 | return 126 | } 127 | 128 | // 业务端口范围是否有重叠 129 | isOverlap := false 130 | // 先对业务端口范围按照范围的起始端口从小到大排序 131 | rangeNum := len(portRangeList) 132 | //quickSort(portRangeList, 0, rangeNum - 1) 133 | sort.Sort(portRanges(portRangeList)) 134 | for index := 0; index < rangeNum-1; index++ { 135 | if portRangeList[index][1] >= portRangeList[index+1][0] { 136 | isOverlap = true 137 | break 138 | } 139 | } 140 | if isOverlap { 141 | err = errors.New("启动失败:配置文件中BusinessList配置的业务端口区间有重叠!") 142 | return 143 | } 144 | return 145 | } 146 | 147 | // 检查配置文件中[master]部分配置的正确性 148 | func checkMaster(mc string, mrs string) (err error) { 149 | // 检查MasterConf指定的主HAProxy配置文件是否存在 150 | if _, e := os.Stat(mc); os.IsNotExist(e) { 151 | err = errors.New("启动失败:配置文件[master]部分中MasterConf指定的主HAProxy配置文件不存在!") 152 | return 153 | } 154 | 155 | // 检查MasterRestartScript指定的主HAProxy重启脚本是否存在 156 | if _, e := os.Stat(mrs); os.IsNotExist(e) { 157 | err = errors.New("启动失败:配置文件中[master]部分中MasterRestartScript指定的主HAProxy重启脚本不存在!") 158 | return 159 | } 160 | return 161 | } 162 | 163 | // 检查配置文件中[store]部分配置的正确性 164 | func checkStore(conf ConfigInfo) (err error) { 165 | // 采用json文件存储时 166 | if conf.StoreScheme == 1 { 167 | storeDir := filepath.Dir(conf.FileToReplaceDB) 168 | if _, err = os.Stat(storeDir); os.IsNotExist(err) || filepath.Ext(conf.FileToReplaceDB) != ".json" { 169 | err = errors.New("启动失败:配置文件中[store]部分的FileToReplaceDB项配置有误!") 170 | return 171 | } 172 | } else if conf.StoreScheme == 0 { // 采用数据库存储时 173 | // DSN(Data Source Name)的格式:[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] 174 | matched, _ := regexp.MatchString(`^.+:.*@(tcp(4|6)?|udp(4|6)?|ip(4|6)?|unix(gram|packet)?)\(.+\)/.+(\?.+=.+(&.+=.+)*)?$`, conf.DBDataSourceName) 175 | if conf.DBDriverName != "mysql" || matched == false { 176 | err = errors.New("启动失败:配置文件[store]部分的DBDriverName或DBDataSourceName配置有误!") 177 | return 178 | } 179 | } else { //配置项StoreScheme有误 180 | err = errors.New("启动失败:配置文件[store]部分的StoreScheme项配置有误") 181 | return 182 | } 183 | return 184 | } 185 | 186 | // 检查配置文件[stats]部分配置的正确性 187 | func checkStats(msp string, ssp string) (err error) { 188 | // \d{2,6}中,和6之间不能有空格 189 | pattern := regexp.MustCompile(`^http://.+:\d{2,6}$`) 190 | mspMatched := pattern.MatchString(msp) 191 | sspMatched := pattern.MatchString(ssp) 192 | if mspMatched == false || sspMatched == false { 193 | err = errors.New("启动失败:配置文件[stats]部分的配置项有误!") 194 | return 195 | } 196 | return 197 | } 198 | 199 | // 检查配置文件中配置项的正确性 200 | func CheckConfig(conf ConfigInfo) (err error) { 201 | 202 | err = checkBusinessList(conf.BusinessList) 203 | if err != nil { 204 | return 205 | } 206 | 207 | err = checkMaster(conf.MasterConf, conf.MasterRestartScript) 208 | if err != nil { 209 | return 210 | } 211 | 212 | err = checkStore(conf) 213 | if err != nil { 214 | return 215 | } 216 | 217 | err = checkStats(conf.MasterStatsPage, conf.SlaveStatsPage) 218 | if err != nil { 219 | return 220 | } 221 | return 222 | } 223 | 224 | func ParseConfig(configPath string) (ci ConfigInfo, err error) { 225 | conf, err := config.ReadDefault(configPath) 226 | if err != nil { 227 | return 228 | } 229 | businessList, _ := conf.String("mode", "BusinessList") 230 | masterConf, _ := conf.String("master", "MasterConf") 231 | masterRestartScript, _ := conf.String("master", "MasterRestartScript") 232 | 233 | copyMethod, _ := conf.Int("slave", "CopyMethod") 234 | slaveServerIp, _ := conf.String("slave", "SlaveServerIp") 235 | slaveServerSSHPort, _ := conf.Int("slave", "SlaveServerSSHPort") 236 | slaveRemoteUser, _ := conf.String("slave", "SlaveRemoteUser") 237 | slaveRemotePasswd, _ := conf.String("slave", "SlaveRemotePasswd") 238 | sshCommandPath, _ := conf.String("slave", "SSHCommandPath") 239 | scpCommandPath, _ := conf.String("slave", "ScpCommandPath") 240 | 241 | slaveConf, _ := conf.String("slave", "SlaveConf") 242 | slaveRestartScript, _ := conf.String("slave", "SlaveRestartScript") 243 | 244 | storeScheme, _ := conf.Int("store", "StoreScheme") 245 | 246 | dbDriverName, _ := conf.String("store", "DBDriverName") 247 | dbDataSourceName, _ := conf.String("store", "DBDataSourceName") 248 | 249 | fileToReplaceDB, _ := conf.String("store", "FileToReplaceDB") 250 | 251 | masterStatsPage, _ := conf.String("stats", "MasterStatsPage") 252 | slaveStatsPage, _ := conf.String("stats", "SlaveStatsPage") 253 | 254 | vip, _ := conf.String("others", "Vip") 255 | 256 | newHAProxyConfPath, _ := conf.String("others", "NewHAProxyConfPath") 257 | 258 | ci = ConfigInfo{ 259 | BusinessList: businessList, 260 | MasterConf: masterConf, 261 | MasterRestartScript: masterRestartScript, 262 | CopyMethod: copyMethod, 263 | SlaveServerIp: slaveServerIp, 264 | SlaveServerSSHPort: slaveServerSSHPort, 265 | SlaveRemoteUser: slaveRemoteUser, 266 | SlaveRemotePasswd: slaveRemotePasswd, 267 | SSHCommandPath: sshCommandPath, 268 | ScpCommandPath: scpCommandPath, 269 | SlaveConf: slaveConf, 270 | SlaveRestartScript: slaveRestartScript, 271 | StoreScheme: storeScheme, 272 | DBDriverName: dbDriverName, 273 | DBDataSourceName: dbDataSourceName, 274 | FileToReplaceDB: fileToReplaceDB, 275 | MasterStatsPage: masterStatsPage, 276 | SlaveStatsPage: slaveStatsPage, 277 | Vip: vip, 278 | NewHAProxyConfPath: newHAProxyConfPath, 279 | } 280 | err = CheckConfig(ci) 281 | return 282 | } 283 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.a 3 | *.6 4 | *.out 5 | _testmain.go 6 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=auth_digest 4 | GOFILES=\ 5 | auth.go\ 6 | digest.go\ 7 | basic.go\ 8 | misc.go\ 9 | md5crypt.go\ 10 | users.go\ 11 | 12 | include $(GOROOT)/src/Make.pkg 13 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/README.md: -------------------------------------------------------------------------------- 1 | HTTP Authentication implementation in Go 2 | ======================================== 3 | 4 | This is an implementation of HTTP Basic and HTTP Digest authentication 5 | in Go language. It is designed as a simple wrapper for 6 | http.RequestHandler functions. 7 | 8 | Features 9 | -------- 10 | 11 | * Supports HTTP Basic and HTTP Digest authentication. 12 | * Supports htpasswd and htdigest formatted files. 13 | * Automatic reloading of password files. 14 | * Pluggable interface for user/password storage. 15 | * Supports MD5 and SHA1 for Basic authentication password storage. 16 | * Configurable Digest nonce cache size with expiration. 17 | * Wrapper for legacy http handlers (http.HandlerFunc interface) 18 | 19 | Example usage 20 | ------------- 21 | 22 | This is a complete working example for Basic auth: 23 | 24 | package main 25 | 26 | import ( 27 | auth "github.com/abbot/go-http-auth" 28 | "fmt" 29 | "net/http" 30 | ) 31 | 32 | func Secret(user, realm string) string { 33 | if user == "john" { 34 | // password is "hello" 35 | return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" 36 | } 37 | return "" 38 | } 39 | 40 | func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { 41 | fmt.Fprintf(w, "

Hello, %s!

", r.Username) 42 | } 43 | 44 | func main() { 45 | authenticator := auth.NewBasicAuthenticator("example.com", Secret) 46 | http.HandleFunc("/", authenticator.Wrap(handle)) 47 | http.ListenAndServe(":8080", nil) 48 | } 49 | 50 | See more examples in the "examples" directory. 51 | 52 | Legal 53 | ----- 54 | 55 | This module is developed under Apache 2.0 license, and can be used for 56 | open and proprietary projects. 57 | 58 | Copyright 2012-2013 Lev Shamardin 59 | 60 | Licensed under the Apache License, Version 2.0 (the "License"); you 61 | may not use this file or any other part of this project except in 62 | compliance with the License. You may obtain a copy of the License at 63 | 64 | http://www.apache.org/licenses/LICENSE-2.0 65 | 66 | Unless required by applicable law or agreed to in writing, software 67 | distributed under the License is distributed on an "AS IS" BASIS, 68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 69 | implied. See the License for the specific language governing 70 | permissions and limitations under the License. 71 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "net/http" 4 | 5 | /* 6 | Request handlers must take AuthenticatedRequest instead of http.Request 7 | */ 8 | type AuthenticatedRequest struct { 9 | http.Request 10 | /* 11 | Authenticated user name. Current API implies that Username is 12 | never empty, which means that authentication is always done 13 | before calling the request handler. 14 | */ 15 | Username string 16 | } 17 | 18 | /* 19 | AuthenticatedHandlerFunc is like http.HandlerFunc, but takes 20 | AuthenticatedRequest instead of http.Request 21 | */ 22 | type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest) 23 | 24 | /* 25 | Authenticator wraps an AuthenticatedHandlerFunc with 26 | authentication-checking code. 27 | 28 | Typical Authenticator usage is something like: 29 | 30 | authenticator := SomeAuthenticator(...) 31 | http.HandleFunc("/", authenticator(my_handler)) 32 | 33 | Authenticator wrapper checks the user authentication and calls the 34 | wrapped function only after authentication has succeeded. Otherwise, 35 | it returns a handler which initiates the authentication procedure. 36 | */ 37 | type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc 38 | 39 | type AuthenticatorInterface interface { 40 | Wrap(AuthenticatedHandlerFunc) http.HandlerFunc 41 | } 42 | 43 | func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc { 44 | return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) { 45 | ar.Header.Set("X-Authenticated-Username", ar.Username) 46 | wrapped(w, &ar.Request) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/basic.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/base64" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type BasicAuth struct { 11 | Realm string 12 | Secrets SecretProvider 13 | } 14 | 15 | /* 16 | Checks the username/password combination from the request. Returns 17 | either an empty string (authentication failed) or the name of the 18 | authenticated user. 19 | 20 | Supports MD5 and SHA1 password entries 21 | */ 22 | func (a *BasicAuth) CheckAuth(r *http.Request) string { 23 | s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 24 | if len(s) != 2 || s[0] != "Basic" { 25 | return "" 26 | } 27 | 28 | b, err := base64.StdEncoding.DecodeString(s[1]) 29 | if err != nil { 30 | return "" 31 | } 32 | pair := strings.SplitN(string(b), ":", 2) 33 | if len(pair) != 2 { 34 | return "" 35 | } 36 | passwd := a.Secrets(pair[0], a.Realm) 37 | if passwd == "" { 38 | return "" 39 | } 40 | if passwd[:5] == "{SHA}" { 41 | d := sha1.New() 42 | d.Write([]byte(pair[1])) 43 | if passwd[5:] != base64.StdEncoding.EncodeToString(d.Sum(nil)) { 44 | return "" 45 | } 46 | } else { 47 | e := NewMD5Entry(passwd) 48 | if e == nil { 49 | return "" 50 | } 51 | if passwd != string(MD5Crypt([]byte(pair[1]), e.Salt, e.Magic)) { 52 | return "" 53 | } 54 | } 55 | return pair[0] 56 | } 57 | 58 | /* 59 | http.Handler for BasicAuth which initiates the authentication process 60 | (or requires reauthentication). 61 | */ 62 | func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { 63 | w.Header().Set("WWW-Authenticate", `Basic realm="`+a.Realm+`"`) 64 | w.WriteHeader(401) 65 | w.Write([]byte("401 Unauthorized\n")) 66 | } 67 | 68 | /* 69 | BasicAuthenticator returns a function, which wraps an 70 | AuthenticatedHandlerFunc converting it to http.HandlerFunc. This 71 | wrapper function checks the authentication and either sends back 72 | required authentication headers, or calls the wrapped function with 73 | authenticated username in the AuthenticatedRequest. 74 | */ 75 | func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc { 76 | return func(w http.ResponseWriter, r *http.Request) { 77 | if username := a.CheckAuth(r); username == "" { 78 | a.RequireAuth(w, r) 79 | } else { 80 | ar := &AuthenticatedRequest{Request: *r, Username: username} 81 | wrapped(w, ar) 82 | } 83 | } 84 | } 85 | 86 | func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth { 87 | return &BasicAuth{Realm: realm, Secrets: secrets} 88 | } 89 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/basic_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestAuthBasic(t *testing.T) { 10 | secrets := HtpasswdFileProvider("test.htpasswd") 11 | a := &BasicAuth{Realm: "example.com", Secrets: secrets} 12 | r := &http.Request{} 13 | r.Method = "GET" 14 | if a.CheckAuth(r) != "" { 15 | t.Fatal("CheckAuth passed on empty headers") 16 | } 17 | r.Header = http.Header(make(map[string][]string)) 18 | r.Header.Set("Authorization", "Digest blabla ololo") 19 | if a.CheckAuth(r) != "" { 20 | t.Fatal("CheckAuth passed on bad headers") 21 | } 22 | r.Header.Set("Authorization", "Basic !@#") 23 | if a.CheckAuth(r) != "" { 24 | t.Fatal("CheckAuth passed on bad base64 data") 25 | } 26 | 27 | data := [][]string{ 28 | {"test", "hello"}, 29 | {"test2", "hello2"}, 30 | } 31 | for _, tc := range data { 32 | auth := base64.StdEncoding.EncodeToString([]byte(tc[0] + ":" + tc[1])) 33 | r.Header.Set("Authorization", "Basic "+auth) 34 | if a.CheckAuth(r) != tc[0] { 35 | t.Fatalf("CheckAuth failed for user '%s'", tc[0]) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/digest.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type digest_client struct { 15 | nc uint64 16 | last_seen int64 17 | } 18 | 19 | type DigestAuth struct { 20 | Realm string 21 | Opaque string 22 | Secrets SecretProvider 23 | PlainTextSecrets bool 24 | 25 | /* 26 | Approximate size of Client's Cache. When actual number of 27 | tracked client nonces exceeds 28 | ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2 29 | older entries are purged. 30 | */ 31 | ClientCacheSize int 32 | ClientCacheTolerance int 33 | 34 | clients map[string]*digest_client 35 | mutex sync.Mutex 36 | } 37 | 38 | type digest_cache_entry struct { 39 | nonce string 40 | last_seen int64 41 | } 42 | 43 | type digest_cache []digest_cache_entry 44 | 45 | func (c digest_cache) Less(i, j int) bool { 46 | return c[i].last_seen < c[j].last_seen 47 | } 48 | 49 | func (c digest_cache) Len() int { 50 | return len(c) 51 | } 52 | 53 | func (c digest_cache) Swap(i, j int) { 54 | c[i], c[j] = c[j], c[i] 55 | } 56 | 57 | /* 58 | Remove count oldest entries from DigestAuth.clients 59 | */ 60 | func (a *DigestAuth) Purge(count int) { 61 | entries := make([]digest_cache_entry, 0, len(a.clients)) 62 | for nonce, client := range a.clients { 63 | entries = append(entries, digest_cache_entry{nonce, client.last_seen}) 64 | } 65 | cache := digest_cache(entries) 66 | sort.Sort(cache) 67 | for _, client := range cache[:count] { 68 | delete(a.clients, client.nonce) 69 | } 70 | } 71 | 72 | /* 73 | http.Handler for DigestAuth which initiates the authentication process 74 | (or requires reauthentication). 75 | */ 76 | func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { 77 | if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance { 78 | a.Purge(a.ClientCacheTolerance * 2) 79 | } 80 | nonce := RandomKey() 81 | a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 82 | w.Header().Set("WWW-Authenticate", 83 | fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`, 84 | a.Realm, nonce, a.Opaque)) 85 | w.WriteHeader(401) 86 | w.Write([]byte("401 Unauthorized\n")) 87 | } 88 | 89 | /* 90 | Parse Authorization header from the http.Request. Returns a map of 91 | auth parameters or nil if the header is not a valid parsable Digest 92 | auth header. 93 | */ 94 | func DigestAuthParams(r *http.Request) map[string]string { 95 | s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 96 | if len(s) != 2 || s[0] != "Digest" { 97 | return nil 98 | } 99 | 100 | result := map[string]string{} 101 | for _, kv := range strings.Split(s[1], ",") { 102 | parts := strings.SplitN(kv, "=", 2) 103 | if len(parts) != 2 { 104 | continue 105 | } 106 | result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ") 107 | } 108 | return result 109 | } 110 | 111 | /* 112 | Check if request contains valid authentication data. Returns a pair 113 | of username, authinfo where username is the name of the authenticated 114 | user or an empty string and authinfo is the contents for the optional 115 | Authentication-Info response header. 116 | */ 117 | func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) { 118 | da.mutex.Lock() 119 | defer da.mutex.Unlock() 120 | username = "" 121 | authinfo = nil 122 | auth := DigestAuthParams(r) 123 | if auth == nil || da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" { 124 | return 125 | } 126 | 127 | // Check if the requested URI matches auth header 128 | switch u, err := url.Parse(auth["uri"]); { 129 | case err != nil: 130 | return 131 | case r.URL == nil: 132 | return 133 | case len(u.Path) > len(r.URL.Path): 134 | return 135 | case !strings.HasPrefix(r.URL.Path, u.Path): 136 | return 137 | } 138 | 139 | HA1 := da.Secrets(auth["username"], da.Realm) 140 | if da.PlainTextSecrets { 141 | HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1) 142 | } 143 | HA2 := H(r.Method + ":" + auth["uri"]) 144 | KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":")) 145 | 146 | if KD != auth["response"] { 147 | return 148 | } 149 | 150 | // At this point crypto checks are completed and validated. 151 | // Now check if the session is valid. 152 | 153 | nc, err := strconv.ParseUint(auth["nc"], 16, 64) 154 | if err != nil { 155 | return 156 | } 157 | 158 | if client, ok := da.clients[auth["nonce"]]; !ok { 159 | return 160 | } else { 161 | if client.nc != 0 && client.nc >= nc { 162 | return 163 | } 164 | client.nc = nc 165 | client.last_seen = time.Now().UnixNano() 166 | } 167 | 168 | resp_HA2 := H(":" + auth["uri"]) 169 | rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":")) 170 | 171 | info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"]) 172 | return auth["username"], &info 173 | } 174 | 175 | /* 176 | Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth 177 | */ 178 | const DefaultClientCacheSize = 1000 179 | const DefaultClientCacheTolerance = 100 180 | 181 | /* 182 | Wrap returns an Authenticator which uses HTTP Digest 183 | authentication. Arguments: 184 | 185 | realm: The authentication realm. 186 | 187 | secrets: SecretProvider which must return HA1 digests for the same 188 | realm as above. 189 | */ 190 | func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc { 191 | return func(w http.ResponseWriter, r *http.Request) { 192 | if username, authinfo := a.CheckAuth(r); username == "" { 193 | a.RequireAuth(w, r) 194 | } else { 195 | ar := &AuthenticatedRequest{Request: *r, Username: username} 196 | if authinfo != nil { 197 | w.Header().Set("Authentication-Info", *authinfo) 198 | } 199 | wrapped(w, ar) 200 | } 201 | } 202 | } 203 | 204 | /* 205 | JustCheck returns function which converts an http.HandlerFunc into a 206 | http.HandlerFunc which requires authentication. Username is passed as 207 | an extra X-Authenticated-Username header. 208 | */ 209 | func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc { 210 | return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) { 211 | ar.Header.Set("X-Authenticated-Username", ar.Username) 212 | wrapped(w, &ar.Request) 213 | }) 214 | } 215 | 216 | func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth { 217 | da := &DigestAuth{ 218 | Opaque: RandomKey(), 219 | Realm: realm, 220 | Secrets: secrets, 221 | PlainTextSecrets: false, 222 | ClientCacheSize: DefaultClientCacheSize, 223 | ClientCacheTolerance: DefaultClientCacheTolerance, 224 | clients: map[string]*digest_client{}} 225 | return da 226 | } 227 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/digest_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestAuthDigest(t *testing.T) { 11 | secrets := HtdigestFileProvider("test.htdigest") 12 | da := &DigestAuth{Opaque: "U7H+ier3Ae8Skd/g", 13 | Realm: "example.com", 14 | Secrets: secrets, 15 | clients: map[string]*digest_client{}} 16 | r := &http.Request{} 17 | r.Method = "GET" 18 | if u, _ := da.CheckAuth(r); u != "" { 19 | t.Fatal("non-empty auth for empty request header") 20 | } 21 | r.Header = http.Header(make(map[string][]string)) 22 | r.Header.Set("Authorization", "Digest blabla") 23 | if u, _ := da.CheckAuth(r); u != "" { 24 | t.Fatal("non-empty auth for bad request header") 25 | } 26 | r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="Vb9BP/h81n3GpTTB", uri="/t2", cnonce="NjE4MTM2", nc=00000001, qop="auth", response="ffc357c4eba74773c8687e0bc724c9a3", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`) 27 | if u, _ := da.CheckAuth(r); u != "" { 28 | t.Fatal("non-empty auth for unknown client") 29 | } 30 | 31 | r.URL, _ = url.Parse("/t2") 32 | da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 33 | if u, _ := da.CheckAuth(r); u != "test" { 34 | t.Fatal("empty auth for legitimate client") 35 | } 36 | if u, _ := da.CheckAuth(r); u != "" { 37 | t.Fatal("non-empty auth for outdated nc") 38 | } 39 | 40 | r.URL, _ = url.Parse("/") 41 | da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 42 | if u, _ := da.CheckAuth(r); u != "" { 43 | t.Fatal("non-empty auth for bad request path") 44 | } 45 | 46 | r.URL, _ = url.Parse("/t3") 47 | da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 48 | if u, _ := da.CheckAuth(r); u != "" { 49 | t.Fatal("non-empty auth for bad request path") 50 | } 51 | 52 | da.clients["+RbVXSbIoa1SaJk1"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 53 | r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="+RbVXSbIoa1SaJk1", uri="/", cnonce="NjE4NDkw", nc=00000001, qop="auth", response="c08918024d7faaabd5424654c4e3ad1c", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`) 54 | if u, _ := da.CheckAuth(r); u != "test" { 55 | t.Fatal("empty auth for valid request in subpath") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/examples/basic.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | /* 4 | Example application using Basic auth 5 | 6 | Build with: 7 | 8 | go build basic.go 9 | */ 10 | 11 | package main 12 | 13 | import ( 14 | auth ".." 15 | "fmt" 16 | "net/http" 17 | ) 18 | 19 | func Secret(user, realm string) string { 20 | if user == "john" { 21 | // password is "hello" 22 | return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" 23 | } 24 | return "" 25 | } 26 | 27 | func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { 28 | fmt.Fprintf(w, "

Hello, %s!

", r.Username) 29 | } 30 | 31 | func main() { 32 | authenticator := auth.NewBasicAuthenticator("example.com", Secret) 33 | http.HandleFunc("/", authenticator.Wrap(handle)) 34 | http.ListenAndServe(":8080", nil) 35 | } 36 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/examples/digest.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | /* 4 | Example application using Digest auth 5 | 6 | Build with: 7 | 8 | go build digest.go 9 | */ 10 | 11 | package main 12 | 13 | import ( 14 | auth ".." 15 | "fmt" 16 | "net/http" 17 | ) 18 | 19 | func Secret(user, realm string) string { 20 | if user == "john" { 21 | // password is "hello" 22 | return "b98e16cbc3d01734b264adba7baa3bf9" 23 | } 24 | return "" 25 | } 26 | 27 | func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { 28 | fmt.Fprintf(w, "

Hello, %s!

", r.Username) 29 | } 30 | 31 | func main() { 32 | authenticator := auth.NewDigestAuthenticator("example.com", Secret) 33 | http.HandleFunc("/", authenticator.Wrap(handle)) 34 | http.ListenAndServe(":8080", nil) 35 | } 36 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/examples/wrapped.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | /* 4 | Example demonstrating how to wrap an application which is unaware of 5 | authenticated requests with a "pass-through" authentication 6 | 7 | Build with: 8 | 9 | go build wrapped.go 10 | */ 11 | 12 | package main 13 | 14 | import ( 15 | auth ".." 16 | "fmt" 17 | "net/http" 18 | ) 19 | 20 | func Secret(user, realm string) string { 21 | if user == "john" { 22 | // password is "hello" 23 | return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" 24 | } 25 | return "" 26 | } 27 | 28 | func regular_handler(w http.ResponseWriter, r *http.Request) { 29 | fmt.Fprintf(w, "

This application is unaware of authentication

") 30 | } 31 | 32 | func main() { 33 | authenticator := auth.NewBasicAuthenticator("example.com", Secret) 34 | http.HandleFunc("/", auth.JustCheck(authenticator, regular_handler)) 35 | http.ListenAndServe(":8080", nil) 36 | } 37 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/md5crypt.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "crypto/md5" 4 | import "strings" 5 | 6 | const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 7 | 8 | var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11} 9 | 10 | type MD5Entry struct { 11 | Magic, Salt, Hash []byte 12 | } 13 | 14 | func NewMD5Entry(e string) *MD5Entry { 15 | parts := strings.SplitN(e, "$", 4) 16 | if len(parts) != 4 { 17 | return nil 18 | } 19 | return &MD5Entry{ 20 | Magic: []byte("$" + parts[1] + "$"), 21 | Salt: []byte(parts[2]), 22 | Hash: []byte(parts[3]), 23 | } 24 | } 25 | 26 | /* 27 | MD5 password crypt implementation 28 | */ 29 | func MD5Crypt(password, salt, magic []byte) []byte { 30 | d := md5.New() 31 | 32 | d.Write(password) 33 | d.Write(magic) 34 | d.Write(salt) 35 | 36 | d2 := md5.New() 37 | d2.Write(password) 38 | d2.Write(salt) 39 | d2.Write(password) 40 | 41 | for i, mixin := 0, d2.Sum(nil); i < len(password); i++ { 42 | d.Write([]byte{mixin[i%16]}) 43 | } 44 | 45 | for i := len(password); i != 0; i >>= 1 { 46 | if i&1 == 0 { 47 | d.Write([]byte{password[0]}) 48 | } else { 49 | d.Write([]byte{0}) 50 | } 51 | } 52 | 53 | final := d.Sum(nil) 54 | 55 | for i := 0; i < 1000; i++ { 56 | d2 := md5.New() 57 | if i&1 == 0 { 58 | d2.Write(final) 59 | } else { 60 | d2.Write(password) 61 | } 62 | 63 | if i%3 != 0 { 64 | d2.Write(salt) 65 | } 66 | 67 | if i%7 != 0 { 68 | d2.Write(password) 69 | } 70 | 71 | if i&1 == 0 { 72 | d2.Write(password) 73 | } else { 74 | d2.Write(final) 75 | } 76 | final = d2.Sum(nil) 77 | } 78 | 79 | result := make([]byte, 0, 22) 80 | v := uint(0) 81 | bits := uint(0) 82 | for _, i := range md5_crypt_swaps { 83 | v |= (uint(final[i]) << bits) 84 | for bits = bits + 8; bits > 6; bits -= 6 { 85 | result = append(result, itoa64[v&0x3f]) 86 | v >>= 6 87 | } 88 | } 89 | result = append(result, itoa64[v&0x3f]) 90 | 91 | return append(append(append(magic, salt...), '$'), result...) 92 | } 93 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/md5crypt_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "testing" 4 | 5 | func Test_MD5Crypt(t *testing.T) { 6 | test_cases := [][]string{ 7 | {"apache", "$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1"}, 8 | {"pass", "$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90"}, 9 | } 10 | for _, tc := range test_cases { 11 | e := NewMD5Entry(tc[1]) 12 | result := MD5Crypt([]byte(tc[0]), e.Salt, e.Magic) 13 | if string(result) != tc[1] { 14 | t.Fatalf("MD5Crypt returned '%s' instead of '%s'", string(result), tc[1]) 15 | } 16 | t.Logf("MD5Crypt: '%s' (%s%s$) -> %s", tc[0], e.Magic, e.Salt, result) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/misc.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "encoding/base64" 4 | import "crypto/md5" 5 | import "crypto/rand" 6 | import "fmt" 7 | 8 | /* 9 | Return a random 16-byte base64 alphabet string 10 | */ 11 | func RandomKey() string { 12 | k := make([]byte, 12) 13 | for bytes := 0; bytes < len(k); { 14 | n, err := rand.Read(k[bytes:]) 15 | if err != nil { 16 | panic("rand.Read() failed") 17 | } 18 | bytes += n 19 | } 20 | return base64.StdEncoding.EncodeToString(k) 21 | } 22 | 23 | /* 24 | H function for MD5 algorithm (returns a lower-case hex MD5 digest) 25 | */ 26 | func H(data string) string { 27 | digest := md5.New() 28 | digest.Write([]byte(data)) 29 | return fmt.Sprintf("%x", digest.Sum(nil)) 30 | } 31 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/misc_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "testing" 4 | 5 | func TestH(t *testing.T) { 6 | const hello = "Hello, world!" 7 | const hello_md5 = "6cd3556deb0da54bca060b4c39479839" 8 | h := H(hello) 9 | if h != hello_md5 { 10 | t.Fatal("Incorrect digest for test string:", h, "instead of", hello_md5) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/test.htdigest: -------------------------------------------------------------------------------- 1 | test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9 2 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/test.htpasswd: -------------------------------------------------------------------------------- 1 | test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00= 2 | test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0 3 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/users.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "encoding/csv" 4 | import "os" 5 | 6 | /* 7 | SecretProvider is used by authenticators. Takes user name and realm 8 | as an argument, returns secret required for authentication (HA1 for 9 | digest authentication, properly encrypted password for basic). 10 | */ 11 | type SecretProvider func(user, realm string) string 12 | 13 | /* 14 | Common functions for file auto-reloading 15 | */ 16 | type File struct { 17 | Path string 18 | Info os.FileInfo 19 | /* must be set in inherited types during initialization */ 20 | Reload func() 21 | } 22 | 23 | func (f *File) ReloadIfNeeded() { 24 | info, err := os.Stat(f.Path) 25 | if err != nil { 26 | panic(err) 27 | } 28 | if f.Info == nil || f.Info.ModTime() != info.ModTime() { 29 | f.Info = info 30 | f.Reload() 31 | } 32 | } 33 | 34 | /* 35 | Structure used for htdigest file authentication. Users map realms to 36 | maps of users to their HA1 digests. 37 | */ 38 | type HtdigestFile struct { 39 | File 40 | Users map[string]map[string]string 41 | } 42 | 43 | func reload_htdigest(hf *HtdigestFile) { 44 | r, err := os.Open(hf.Path) 45 | if err != nil { 46 | panic(err) 47 | } 48 | csv_reader := csv.NewReader(r) 49 | csv_reader.Comma = ':' 50 | csv_reader.Comment = '#' 51 | csv_reader.TrimLeadingSpace = true 52 | 53 | records, err := csv_reader.ReadAll() 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | hf.Users = make(map[string]map[string]string) 59 | for _, record := range records { 60 | _, exists := hf.Users[record[1]] 61 | if !exists { 62 | hf.Users[record[1]] = make(map[string]string) 63 | } 64 | hf.Users[record[1]][record[0]] = record[2] 65 | } 66 | } 67 | 68 | /* 69 | SecretProvider implementation based on htdigest-formated files. Will 70 | reload htdigest file on changes. Will panic on syntax errors in 71 | htdigest files. 72 | */ 73 | func HtdigestFileProvider(filename string) SecretProvider { 74 | hf := &HtdigestFile{File: File{Path: filename}} 75 | hf.Reload = func() { reload_htdigest(hf) } 76 | return func(user, realm string) string { 77 | hf.ReloadIfNeeded() 78 | _, exists := hf.Users[realm] 79 | if !exists { 80 | return "" 81 | } 82 | digest, exists := hf.Users[realm][user] 83 | if !exists { 84 | return "" 85 | } 86 | return digest 87 | } 88 | } 89 | 90 | /* 91 | Structure used for htdigest file authentication. Users map users to 92 | their salted encrypted password 93 | */ 94 | type HtpasswdFile struct { 95 | File 96 | Users map[string]string 97 | } 98 | 99 | func reload_htpasswd(h *HtpasswdFile) { 100 | r, err := os.Open(h.Path) 101 | if err != nil { 102 | panic(err) 103 | } 104 | csv_reader := csv.NewReader(r) 105 | csv_reader.Comma = ':' 106 | csv_reader.Comment = '#' 107 | csv_reader.TrimLeadingSpace = true 108 | 109 | records, err := csv_reader.ReadAll() 110 | if err != nil { 111 | panic(err) 112 | } 113 | 114 | h.Users = make(map[string]string) 115 | for _, record := range records { 116 | h.Users[record[0]] = record[1] 117 | } 118 | } 119 | 120 | /* 121 | SecretProvider implementation based on htpasswd-formated files. Will 122 | reload htpasswd file on changes. Will panic on syntax errors in 123 | htpasswd files. Realm argument of the SecretProvider is ignored. 124 | */ 125 | func HtpasswdFileProvider(filename string) SecretProvider { 126 | h := &HtpasswdFile{File: File{Path: filename}} 127 | h.Reload = func() { reload_htpasswd(h) } 128 | return func(user, realm string) string { 129 | h.ReloadIfNeeded() 130 | password, exists := h.Users[user] 131 | if !exists { 132 | return "" 133 | } 134 | return password 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/github.com/abbot/go-http-auth/users_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHtdigestFile(t *testing.T) { 8 | secrets := HtdigestFileProvider("test.htdigest") 9 | digest := secrets("test", "example.com") 10 | if digest != "aa78524fceb0e50fd8ca96dd818b8cf9" { 11 | t.Fatal("Incorrect digest for test user:", digest) 12 | } 13 | digest = secrets("test", "example1.com") 14 | if digest != "" { 15 | t.Fatal("Got digest for user in non-existant realm:", digest) 16 | } 17 | digest = secrets("test1", "example.com") 18 | if digest != "" { 19 | t.Fatal("Got digest for non-existant user:", digest) 20 | } 21 | } 22 | 23 | func TestHtpasswdFile(t *testing.T) { 24 | secrets := HtpasswdFileProvider("test.htpasswd") 25 | passwd := secrets("test", "blah") 26 | if passwd != "{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=" { 27 | t.Fatal("Incorrect passwd for test user:", passwd) 28 | } 29 | passwd = secrets("test3", "blah") 30 | if passwd != "" { 31 | t.Fatal("Got passwd for non-existant user:", passwd) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/sshoperation/sshoperation.go: -------------------------------------------------------------------------------- 1 | package sshoperation 2 | 3 | import ( 4 | "os/exec" 5 | "config" 6 | "fmt" 7 | "io/ioutil" 8 | "errors" 9 | "code.google.com/p/go.crypto/ssh" 10 | ) 11 | 12 | type clientPassword string 13 | 14 | func (p clientPassword) Password(user string) (string, error) { 15 | return string(p), nil 16 | } 17 | 18 | func ScpHaproxyConf(appConf config.ConfigInfo) (errinfo error) { 19 | 20 | server := fmt.Sprintf("%s:%d", appConf.SlaveServerIp, appConf.SlaveServerSSHPort) 21 | username := appConf.SlaveRemoteUser 22 | password := clientPassword(appConf.SlaveRemotePasswd) 23 | 24 | // An SSH client is represented with a slete). Currently only 25 | // the "password" authentication method is supported. 26 | // 27 | // To authenticate with the remote server you must pass at least one 28 | // implementation of ClientAuth via the Auth field in ClientConfig. 29 | 30 | conf := &ssh.ClientConfig{ 31 | User: username, 32 | Auth: []ssh.ClientAuth{ 33 | // ClientAuthPassword wraps a ClientPassword implementation 34 | // in a type that implements ClientAuth. 35 | ssh.ClientAuthPassword(password), 36 | }, 37 | } 38 | client, err := ssh.Dial("tcp", server, conf) 39 | if err != nil { 40 | errinfo = errors.New(fmt.Sprintf("Failed to dial: %s", err.Error())) 41 | return 42 | } 43 | 44 | // Each ClientConn can support multiple interactive sessions, 45 | // represented by a Session. 46 | defer client.Close() 47 | // Create a session 48 | session, err := client.NewSession() 49 | if err != nil { 50 | errinfo = errors.New(fmt.Sprintf("unable to create session: %s", err.Error())) 51 | return 52 | } 53 | defer session.Close() 54 | 55 | confBytes, err := ioutil.ReadFile(appConf.NewHAProxyConfPath) 56 | if err != nil { 57 | errinfo = errors.New(fmt.Sprintf("Failed to run: %s", err.Error())) 58 | return 59 | } 60 | content := string(confBytes) 61 | go func() { 62 | w, _ := session.StdinPipe() 63 | defer w.Close() 64 | fmt.Fprintln(w, "C0644", len(content), "new_conf") 65 | fmt.Fprint(w, content) 66 | fmt.Fprint(w, "\x00") 67 | }() 68 | cmd := fmt.Sprintf("%s -tq %s && %s", appConf.ScpCommandPath, appConf.SlaveConf, appConf.SlaveRestartScript) 69 | if err := session.Run(cmd); err != nil { 70 | errinfo = errors.New(fmt.Sprintf("Failed to run: %s", err.Error())) 71 | return 72 | } 73 | return 74 | } 75 | 76 | // 主到从已打通信任关系,故无需密码 77 | func ConfidentialScpHAProxyConf(appConf config.ConfigInfo) (errinfo error) { 78 | scpTarget := fmt.Sprintf("%s@%s:%s", appConf.SlaveRemoteUser, appConf.SlaveServerIp, appConf.SlaveConf) 79 | scpTargetPortOption := fmt.Sprintf("-P %d", appConf.SlaveServerSSHPort) 80 | _, err := exec.Command(appConf.ScpCommandPath, scpTargetPortOption, appConf.NewHAProxyConfPath, scpTarget).Output() 81 | if err != nil { 82 | errinfo = errors.New(fmt.Sprintf("Failed to remote copy config file : %s", err.Error())) 83 | return 84 | } 85 | sshTarget := fmt.Sprintf("%s@%s", appConf.SlaveRemoteUser, appConf.SlaveServerIp) 86 | sshTargetPortOption := fmt.Sprintf("-p %d", appConf.SlaveServerSSHPort) 87 | _, err = exec.Command(appConf.SSHCommandPath, sshTargetPortOption, sshTarget, appConf.SlaveRestartScript).Output() 88 | if err != nil { 89 | errinfo = errors.New(fmt.Sprintf("Falied to restart haproxy : %s", err.Error())) 90 | return 91 | } 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /src/tools/tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "config" 5 | "database/sql" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "os" 11 | ) 12 | 13 | func CheckError(err error) { 14 | if err != nil { 15 | fmt.Println(err) 16 | } 17 | } 18 | 19 | func StorageTransform(appConf config.ConfigInfo) (err error) { 20 | 21 | type DataRow struct { 22 | Id int 23 | Servers string 24 | BackupServers string 25 | VPort int 26 | Comment string 27 | LogOrNot int 28 | DateTime string 29 | } 30 | 31 | db, err := sql.Open(appConf.DBDriverName, appConf.DBDataSourceName) 32 | defer db.Close() 33 | CheckError(err) 34 | 35 | if appConf.StoreScheme == 0 { 36 | fmt.Println("**从数据库读取数据存入JSON文件中**") 37 | rows, err := db.Query("SELECT id, servers, backup_servers, vport, comment, logornot, datetime FROM haproxymapinfo ORDER BY vport ASC") 38 | CheckError(err) 39 | var id int 40 | var servers string 41 | var backupServers string 42 | var vport int 43 | var comment string 44 | var logornot int 45 | var datetime string 46 | taskList := make([]DataRow, 0, 100) 47 | for rows.Next() { 48 | err = rows.Scan(&id, &servers, &backupServers, &vport, &comment, &logornot, &datetime) 49 | taskList = append(taskList, DataRow{Id: id, Servers: servers, BackupServers: backupServers, VPort: vport, Comment: comment, LogOrNot: logornot, DateTime: datetime}) 50 | } 51 | fmt.Printf("共%d条数据\n", len(taskList)) 52 | dataJson, err := json.MarshalIndent(taskList, "", " ") 53 | CheckError(err) 54 | f, err := os.OpenFile(appConf.FileToReplaceDB, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666) 55 | CheckError(err) 56 | defer f.Close() 57 | f.Write(dataJson) 58 | f.Sync() 59 | return nil 60 | } 61 | if appConf.StoreScheme == 1 { 62 | fmt.Println("**从JSON文件读取数据插入数据库中**") 63 | bytes, err := ioutil.ReadFile(appConf.FileToReplaceDB) 64 | allData := make([]DataRow, 0, 100) 65 | err = json.Unmarshal(bytes, &allData) 66 | CheckError(err) 67 | // 这里还得先测试数据表haproxy是否存在,若不存在,则需创建 68 | fmt.Printf("将插入%d条数据\n", len(allData)) 69 | var num int64 70 | num = 0 71 | for _, data := range allData { 72 | result, err := db.Exec("INSERT INTO haproxymapinfo (id, servers, backup_servers, vport, comment, logornot, datetime) VALUES (?, ?, ?, ?, ?, ?)", data.Id, data.Servers, data.VPort, data.Comment, data.LogOrNot, data.DateTime) 73 | CheckError(err) 74 | n, err := result.RowsAffected() 75 | CheckError(err) 76 | num += n 77 | } 78 | fmt.Printf("共插入数据%d条\n", num) 79 | return nil 80 | } 81 | 82 | return errors.New("存储方式不正确") 83 | } 84 | -------------------------------------------------------------------------------- /static/alertify/themes/alertify.bootstrap.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Twitter Bootstrap Look and Feel 3 | * Based on http://twitter.github.com/bootstrap/ 4 | */ 5 | .alertify, 6 | .alertify-log { 7 | font-family: sans-serif; 8 | } 9 | .alertify { 10 | background: #FFF; 11 | border: 1px solid #8E8E8E; /* browsers that don't support rgba */ 12 | border: 1px solid rgba(0,0,0,.3); 13 | border-radius: 6px; 14 | box-shadow: 0 3px 7px rgba(0,0,0,.3); 15 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 16 | -moz-background-clip: padding; /* Firefox 3.6 */ 17 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */ 18 | } 19 | .alertify-dialog { 20 | padding: 0; 21 | } 22 | .alertify-inner { 23 | text-align: left; 24 | } 25 | .alertify-message { 26 | padding: 15px; 27 | margin: 0; 28 | } 29 | .alertify-text-wrapper { 30 | padding: 0 15px; 31 | } 32 | .alertify-text { 33 | color: #555; 34 | border-radius: 4px; 35 | padding: 8px; 36 | background-color: #FFF; 37 | border: 1px solid #CCC; 38 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075); 39 | } 40 | .alertify-text:focus { 41 | border-color: rgba(82,168,236,.8); 42 | outline: 0; 43 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); 44 | } 45 | 46 | .alertify-buttons { 47 | padding: 14px 15px 15px; 48 | background: #F5F5F5; 49 | border-top: 1px solid #DDD; 50 | border-radius: 0 0 6px 6px; 51 | box-shadow: inset 0 1px 0 #FFF; 52 | text-align: right; 53 | } 54 | .alertify-button, 55 | .alertify-button:hover, 56 | .alertify-button:focus, 57 | .alertify-button:active { 58 | margin-left: 10px; 59 | border-radius: 4px; 60 | font-weight: normal; 61 | padding: 4px 12px; 62 | text-decoration: none; 63 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(0, 0, 0, .05); 64 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 65 | background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 66 | background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 67 | background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 68 | background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 69 | } 70 | .alertify-button:focus { 71 | outline: none; 72 | box-shadow: 0 0 5px #2B72D5; 73 | } 74 | .alertify-button:active { 75 | position: relative; 76 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 77 | } 78 | .alertify-button-cancel, 79 | .alertify-button-cancel:hover, 80 | .alertify-button-cancel:focus, 81 | .alertify-button-cancel:active { 82 | text-shadow: 0 -1px 0 rgba(255,255,255,.75); 83 | background-color: #E6E6E6; 84 | border: 1px solid #BBB; 85 | color: #333; 86 | background-image: -webkit-linear-gradient(top, #FFF, #E6E6E6); 87 | background-image: -moz-linear-gradient(top, #FFF, #E6E6E6); 88 | background-image: -ms-linear-gradient(top, #FFF, #E6E6E6); 89 | background-image: -o-linear-gradient(top, #FFF, #E6E6E6); 90 | background-image: linear-gradient(top, #FFF, #E6E6E6); 91 | } 92 | .alertify-button-cancel:hover, 93 | .alertify-button-cancel:focus, 94 | .alertify-button-cancel:active { 95 | background: #E6E6E6; 96 | } 97 | .alertify-button-ok, 98 | .alertify-button-ok:hover, 99 | .alertify-button-ok:focus, 100 | .alertify-button-ok:active { 101 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 102 | background-color: #04C; 103 | border: 1px solid #04C; 104 | border-color: #04C #04C #002A80; 105 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 106 | color: #FFF; 107 | } 108 | .alertify-button-ok:hover, 109 | .alertify-button-ok:focus, 110 | .alertify-button-ok:active { 111 | background: #04C; 112 | } 113 | 114 | .alertify-log { 115 | background: #D9EDF7; 116 | padding: 8px 14px; 117 | border-radius: 4px; 118 | color: #3A8ABF; 119 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 120 | border: 1px solid #BCE8F1; 121 | } 122 | .alertify-log-error { 123 | color: #B94A48; 124 | background: #F2DEDE; 125 | border: 1px solid #EED3D7; 126 | } 127 | .alertify-log-success { 128 | color: #468847; 129 | background: #DFF0D8; 130 | border: 1px solid #D6E9C6; 131 | } -------------------------------------------------------------------------------- /static/alertify/themes/alertify.core.css: -------------------------------------------------------------------------------- 1 | .alertify, 2 | .alertify-show, 3 | .alertify-log { 4 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 5 | -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 6 | -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 7 | -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 8 | transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */ 9 | } 10 | .alertify-hide { 11 | -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 12 | -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 14 | -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 15 | transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 16 | } 17 | .alertify-log-hide { 18 | -webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 19 | -moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 20 | -ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 21 | -o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 22 | transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 23 | } 24 | .alertify-cover { 25 | position: fixed; z-index: 99999; 26 | top: 0; right: 0; bottom: 0; left: 0; 27 | background-color:white; 28 | filter:alpha(opacity=0); 29 | opacity:0; 30 | } 31 | .alertify-cover-hidden { 32 | display: none; 33 | } 34 | .alertify { 35 | position: fixed; z-index: 99999; 36 | top: 50px; left: 50%; 37 | width: 550px; 38 | margin-left: -275px; 39 | opacity: 1; 40 | } 41 | .alertify-hidden { 42 | -webkit-transform: translate(0,-150px); 43 | -moz-transform: translate(0,-150px); 44 | -ms-transform: translate(0,-150px); 45 | -o-transform: translate(0,-150px); 46 | transform: translate(0,-150px); 47 | opacity: 0; 48 | display: none; 49 | } 50 | /* overwrite display: none; for everything except IE6-8 */ 51 | :root *> .alertify-hidden { 52 | display: block; 53 | visibility: hidden; 54 | } 55 | .alertify-logs { 56 | position: fixed; 57 | z-index: 5000; 58 | bottom: 10px; 59 | right: 10px; 60 | width: 300px; 61 | } 62 | .alertify-logs-hidden { 63 | display: none; 64 | } 65 | .alertify-log { 66 | display: block; 67 | margin-top: 10px; 68 | position: relative; 69 | right: -300px; 70 | opacity: 0; 71 | } 72 | .alertify-log-show { 73 | right: 0; 74 | opacity: 1; 75 | } 76 | .alertify-log-hide { 77 | -webkit-transform: translate(300px, 0); 78 | -moz-transform: translate(300px, 0); 79 | -ms-transform: translate(300px, 0); 80 | -o-transform: translate(300px, 0); 81 | transform: translate(300px, 0); 82 | opacity: 0; 83 | } 84 | .alertify-dialog { 85 | padding: 25px; 86 | } 87 | .alertify-resetFocus { 88 | border: 0; 89 | clip: rect(0 0 0 0); 90 | height: 1px; 91 | margin: -1px; 92 | overflow: hidden; 93 | padding: 0; 94 | position: absolute; 95 | width: 1px; 96 | } 97 | .alertify-inner { 98 | text-align: center; 99 | } 100 | .alertify-text { 101 | margin-bottom: 15px; 102 | width: 100%; 103 | -webkit-box-sizing: border-box; 104 | -moz-box-sizing: border-box; 105 | box-sizing: border-box; 106 | font-size: 100%; 107 | } 108 | .alertify-buttons { 109 | } 110 | .alertify-button, 111 | .alertify-button:hover, 112 | .alertify-button:active, 113 | .alertify-button:visited { 114 | background: none; 115 | text-decoration: none; 116 | border: none; 117 | /* line-height and font-size for input button */ 118 | line-height: 1.5; 119 | font-size: 100%; 120 | display: inline-block; 121 | cursor: pointer; 122 | margin-left: 5px; 123 | } 124 | 125 | .alertify-isHidden { 126 | display: none; 127 | } 128 | 129 | @media only screen and (max-width: 680px) { 130 | .alertify, 131 | .alertify-logs { 132 | width: 90%; 133 | -webkit-box-sizing: border-box; 134 | -moz-box-sizing: border-box; 135 | box-sizing: border-box; 136 | } 137 | .alertify { 138 | left: 5%; 139 | margin: 0; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /static/alertify/themes/alertify.default.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Look and Feel 3 | */ 4 | .alertify, 5 | .alertify-log { 6 | font-family: sans-serif; 7 | } 8 | .alertify { 9 | background: #FFF; 10 | border: 10px solid #333; /* browsers that don't support rgba */ 11 | border: 10px solid rgba(0,0,0,.7); 12 | border-radius: 8px; 13 | box-shadow: 0 3px 3px rgba(0,0,0,.3); 14 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 15 | -moz-background-clip: padding; /* Firefox 3.6 */ 16 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */ 17 | } 18 | .alertify-text { 19 | border: 1px solid #CCC; 20 | padding: 10px; 21 | border-radius: 4px; 22 | } 23 | .alertify-button { 24 | border-radius: 4px; 25 | color: #FFF; 26 | font-weight: bold; 27 | padding: 6px 15px; 28 | text-decoration: none; 29 | text-shadow: 1px 1px 0 rgba(0,0,0,.5); 30 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,.5); 31 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 32 | background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 33 | background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 34 | background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 35 | background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 36 | } 37 | .alertify-button:hover, 38 | .alertify-button:focus { 39 | outline: none; 40 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 41 | background-image: -moz-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 42 | background-image: -ms-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 43 | background-image: -o-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 44 | background-image: linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 45 | } 46 | .alertify-button:focus { 47 | box-shadow: 0 0 15px #2B72D5; 48 | } 49 | .alertify-button:active { 50 | position: relative; 51 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 52 | } 53 | .alertify-button-cancel, 54 | .alertify-button-cancel:hover, 55 | .alertify-button-cancel:focus { 56 | background-color: #FE1A00; 57 | border: 1px solid #D83526; 58 | } 59 | .alertify-button-ok, 60 | .alertify-button-ok:hover, 61 | .alertify-button-ok:focus { 62 | background-color: #5CB811; 63 | border: 1px solid #3B7808; 64 | } 65 | 66 | .alertify-log { 67 | background: #1F1F1F; 68 | background: rgba(0,0,0,.9); 69 | padding: 15px; 70 | border-radius: 4px; 71 | color: #FFF; 72 | text-shadow: -1px -1px 0 rgba(0,0,0,.5); 73 | } 74 | .alertify-log-error { 75 | background: #FE1A00; 76 | background: rgba(254,26,0,.9); 77 | } 78 | .alertify-log-success { 79 | background: #5CB811; 80 | background: rgba(92,184,17,.9); 81 | } -------------------------------------------------------------------------------- /static/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitelife/haproxyconsole/943b7910b9faaaea3733db468dd16405c4933071/static/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /static/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitelife/haproxyconsole/943b7910b9faaaea3733db468dd16405c4933071/static/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | .ui-box { 2 | margin: 80px 0px 0px 0px; 3 | background: white; 4 | border: 1px solid #ccc; 5 | border-top-color: #DADADA; 6 | border-radius: 4px; 7 | box-shadow: 1px 1px 10px #BCBCBC; 8 | color: #464646; 9 | } 10 | 11 | .ui-box-header { 12 | position: relative; 13 | padding: 0px 10px; 14 | border-bottom: 1px solid #dadada; 15 | border-radius: 3px 3px 0 0; 16 | font: 14px/1.6 "Helvetica", arial, sans-serif; 17 | margin-bottom: 10px; 18 | } 19 | 20 | .ui-box-container { 21 | padding: 8px 10px; 22 | line-height: 1.6; 23 | } 24 | 25 | /* 26 | .form-horizontal #controls-server-list { 27 | margin-left: 0px; 28 | text-align: center; 29 | } 30 | */ 31 | #button-group { 32 | float: right; 33 | margin-right: 80px; 34 | } 35 | 36 | #preview-table { 37 | display: none; 38 | margin-top: 10px; 39 | } 40 | 41 | #submit, #edit-submit, #step-back, #edit-step-back { 42 | display: none; 43 | } 44 | 45 | /* 46 | #submit, #preview { 47 | margin-right: 20px; 48 | } 49 | */ 50 | 51 | #result { 52 | margin-top: 10px; 53 | text-align: center; 54 | } 55 | 56 | #listenlist-div { 57 | margin-top: 75px; 58 | } 59 | 60 | .listenlist-table th, .listenlist-table td { 61 | text-align: center; 62 | } 63 | 64 | #edit-div, #edit-preview-table, .id { 65 | display: none; 66 | } 67 | 68 | #button-apply-conf { 69 | margin-bottom: 20px; 70 | float: right; 71 | margin-right: 20px; 72 | } 73 | 74 | #apply-to-master { 75 | margin-right: 10px; 76 | } 77 | 78 | .statspage-iframe { 79 | margin-top: 50px; 80 | } 81 | 82 | .statspage-iframe iframe { 83 | height: 1000px; 84 | width: 100%; 85 | border: none; 86 | } 87 | 88 | #to-specify-port { 89 | display: none; 90 | } -------------------------------------------------------------------------------- /template/footer.tmpl: -------------------------------------------------------------------------------- 1 | {{define "footer"}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{end}} -------------------------------------------------------------------------------- /template/header.tmpl: -------------------------------------------------------------------------------- 1 | {{define "header"}} 2 | 3 | 4 | VIDC链路易迅侧虚拟ip端口申请 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
29 |
30 | {{end}} -------------------------------------------------------------------------------- /template/index.tmpl: -------------------------------------------------------------------------------- 1 | {{define "index"}}{{template "header"}} 2 |
3 |
4 |
5 |

新增TCP负载均衡任务

6 |
7 |
8 |
9 |
10 | 11 |
12 | 16 | 20 |
21 |
22 | {{if .Mode}} 23 |
24 | 25 |
26 | 31 |
32 |
33 | {{end}} 34 |
35 | 36 |
37 | 38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 | 50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 |
59 | 60 |
61 | 64 | 67 |
68 |
69 |
70 |
71 | 72 | 73 | 74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
序号ipport
89 |
90 |
91 | {{template "footer"}} 92 | {{end}} -------------------------------------------------------------------------------- /template/listenlist.tmpl: -------------------------------------------------------------------------------- 1 | {{define "listenlist"}}{{template "header"}} 2 |
3 |
4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{range .ListenTaskList}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {{end}} 35 | 36 |
ID后端机器列表Backup机器列表VipPort说明开启日志申请/更新时间
{{.Seq}}{{.Id}}{{.Servers}}{{.BackupServers}}{{.Vip}}{{.Vport}}{{.Comment}}{{if .LogOrNot}}是{{else}}否{{end}}{{.DateTime}}
37 |
38 |
39 |
40 |
41 |

修改TCP负载均衡任务

42 |
43 |
44 |
45 |
46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 | 65 |
66 | 69 | 72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
序号ipport
95 |
96 |
97 | {{template "footer"}} 98 | {{end}} 99 | -------------------------------------------------------------------------------- /template/statspage.tmpl: -------------------------------------------------------------------------------- 1 | {{define "statspage"}}{{template "header"}} 2 |
3 | 4 |
5 | {{end}}} --------------------------------------------------------------------------------