client.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. /*
  2. Copyright (c) 2014-2018 VMware, Inc. All Rights Reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package soap
  14. import (
  15. "bufio"
  16. "bytes"
  17. "context"
  18. "crypto/sha1"
  19. "crypto/tls"
  20. "crypto/x509"
  21. "encoding/json"
  22. "errors"
  23. "fmt"
  24. "io"
  25. "io/ioutil"
  26. "log"
  27. "net"
  28. "net/http"
  29. "net/http/cookiejar"
  30. "net/url"
  31. "os"
  32. "path/filepath"
  33. "regexp"
  34. "strings"
  35. "sync"
  36. "time"
  37. "github.com/vmware/govmomi/vim25/progress"
  38. "github.com/vmware/govmomi/vim25/types"
  39. "github.com/vmware/govmomi/vim25/xml"
  40. )
  41. type HasFault interface {
  42. Fault() *Fault
  43. }
  44. type RoundTripper interface {
  45. RoundTrip(ctx context.Context, req, res HasFault) error
  46. }
  47. const (
  48. SessionCookieName = "vmware_soap_session"
  49. )
  50. type Client struct {
  51. http.Client
  52. u *url.URL
  53. k bool // Named after curl's -k flag
  54. d *debugContainer
  55. t *http.Transport
  56. hostsMu sync.Mutex
  57. hosts map[string]string
  58. Namespace string // Vim namespace
  59. Version string // Vim version
  60. UserAgent string
  61. cookie string
  62. }
  63. var schemeMatch = regexp.MustCompile(`^\w+://`)
  64. type errInvalidCACertificate struct {
  65. File string
  66. }
  67. func (e errInvalidCACertificate) Error() string {
  68. return fmt.Sprintf(
  69. "invalid certificate '%s', cannot be used as a trusted CA certificate",
  70. e.File,
  71. )
  72. }
  73. // ParseURL is wrapper around url.Parse, where Scheme defaults to "https" and Path defaults to "/sdk"
  74. func ParseURL(s string) (*url.URL, error) {
  75. var err error
  76. var u *url.URL
  77. if s != "" {
  78. // Default the scheme to https
  79. if !schemeMatch.MatchString(s) {
  80. s = "https://" + s
  81. }
  82. u, err = url.Parse(s)
  83. if err != nil {
  84. return nil, err
  85. }
  86. // Default the path to /sdk
  87. if u.Path == "" {
  88. u.Path = "/sdk"
  89. }
  90. if u.User == nil {
  91. u.User = url.UserPassword("", "")
  92. }
  93. }
  94. return u, nil
  95. }
  96. func NewClient(u *url.URL, insecure bool) *Client {
  97. c := Client{
  98. u: u,
  99. k: insecure,
  100. d: newDebug(),
  101. }
  102. // Initialize http.RoundTripper on client, so we can customize it below
  103. if t, ok := http.DefaultTransport.(*http.Transport); ok {
  104. c.t = &http.Transport{
  105. Proxy: t.Proxy,
  106. DialContext: t.DialContext,
  107. MaxIdleConns: t.MaxIdleConns,
  108. IdleConnTimeout: t.IdleConnTimeout,
  109. TLSHandshakeTimeout: t.TLSHandshakeTimeout,
  110. ExpectContinueTimeout: t.ExpectContinueTimeout,
  111. }
  112. } else {
  113. c.t = new(http.Transport)
  114. }
  115. c.hosts = make(map[string]string)
  116. c.t.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.k}
  117. // Don't bother setting DialTLS if InsecureSkipVerify=true
  118. if !c.k {
  119. c.t.DialTLS = c.dialTLS
  120. }
  121. c.Client.Transport = c.t
  122. c.Client.Jar, _ = cookiejar.New(nil)
  123. // Remove user information from a copy of the URL
  124. c.u = c.URL()
  125. c.u.User = nil
  126. return &c
  127. }
  128. // NewServiceClient creates a NewClient with the given URL.Path and namespace.
  129. func (c *Client) NewServiceClient(path string, namespace string) *Client {
  130. vc := c.URL()
  131. u, err := url.Parse(path)
  132. if err != nil {
  133. log.Panicf("url.Parse(%q): %s", path, err)
  134. }
  135. if u.Host == "" {
  136. u.Scheme = vc.Scheme
  137. u.Host = vc.Host
  138. }
  139. client := NewClient(u, c.k)
  140. client.Namespace = "urn:" + namespace
  141. if cert := c.Certificate(); cert != nil {
  142. client.SetCertificate(*cert)
  143. }
  144. // Copy the trusted thumbprints
  145. c.hostsMu.Lock()
  146. for k, v := range c.hosts {
  147. client.hosts[k] = v
  148. }
  149. c.hostsMu.Unlock()
  150. // Copy the cookies
  151. client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u))
  152. // Set SOAP Header cookie
  153. for _, cookie := range client.Jar.Cookies(u) {
  154. if cookie.Name == SessionCookieName {
  155. client.cookie = cookie.Value
  156. break
  157. }
  158. }
  159. // Copy any query params (e.g. GOVMOMI_TUNNEL_PROXY_PORT used in testing)
  160. client.u.RawQuery = vc.RawQuery
  161. return client
  162. }
  163. // SetRootCAs defines the set of root certificate authorities
  164. // that clients use when verifying server certificates.
  165. // By default TLS uses the host's root CA set.
  166. //
  167. // See: http.Client.Transport.TLSClientConfig.RootCAs
  168. func (c *Client) SetRootCAs(file string) error {
  169. pool := x509.NewCertPool()
  170. for _, name := range filepath.SplitList(file) {
  171. pem, err := ioutil.ReadFile(name)
  172. if err != nil {
  173. return err
  174. }
  175. if ok := pool.AppendCertsFromPEM(pem); !ok {
  176. return errInvalidCACertificate{
  177. File: name,
  178. }
  179. }
  180. }
  181. c.t.TLSClientConfig.RootCAs = pool
  182. return nil
  183. }
  184. // Add default https port if missing
  185. func hostAddr(addr string) string {
  186. _, port := splitHostPort(addr)
  187. if port == "" {
  188. return addr + ":443"
  189. }
  190. return addr
  191. }
  192. // SetThumbprint sets the known certificate thumbprint for the given host.
  193. // A custom DialTLS function is used to support thumbprint based verification.
  194. // We first try tls.Dial with the default tls.Config, only falling back to thumbprint verification
  195. // if it fails with an x509.UnknownAuthorityError or x509.HostnameError
  196. //
  197. // See: http.Client.Transport.DialTLS
  198. func (c *Client) SetThumbprint(host string, thumbprint string) {
  199. host = hostAddr(host)
  200. c.hostsMu.Lock()
  201. if thumbprint == "" {
  202. delete(c.hosts, host)
  203. } else {
  204. c.hosts[host] = thumbprint
  205. }
  206. c.hostsMu.Unlock()
  207. }
  208. // Thumbprint returns the certificate thumbprint for the given host if known to this client.
  209. func (c *Client) Thumbprint(host string) string {
  210. host = hostAddr(host)
  211. c.hostsMu.Lock()
  212. defer c.hostsMu.Unlock()
  213. return c.hosts[host]
  214. }
  215. // LoadThumbprints from file with the give name.
  216. // If name is empty or name does not exist this function will return nil.
  217. func (c *Client) LoadThumbprints(file string) error {
  218. if file == "" {
  219. return nil
  220. }
  221. for _, name := range filepath.SplitList(file) {
  222. err := c.loadThumbprints(name)
  223. if err != nil {
  224. return err
  225. }
  226. }
  227. return nil
  228. }
  229. func (c *Client) loadThumbprints(name string) error {
  230. f, err := os.Open(name)
  231. if err != nil {
  232. if os.IsNotExist(err) {
  233. return nil
  234. }
  235. return err
  236. }
  237. scanner := bufio.NewScanner(f)
  238. for scanner.Scan() {
  239. e := strings.SplitN(scanner.Text(), " ", 2)
  240. if len(e) != 2 {
  241. continue
  242. }
  243. c.SetThumbprint(e[0], e[1])
  244. }
  245. _ = f.Close()
  246. return scanner.Err()
  247. }
  248. // ThumbprintSHA1 returns the thumbprint of the given cert in the same format used by the SDK and Client.SetThumbprint.
  249. //
  250. // See: SSLVerifyFault.Thumbprint, SessionManagerGenericServiceTicket.Thumbprint, HostConnectSpec.SslThumbprint
  251. func ThumbprintSHA1(cert *x509.Certificate) string {
  252. sum := sha1.Sum(cert.Raw)
  253. hex := make([]string, len(sum))
  254. for i, b := range sum {
  255. hex[i] = fmt.Sprintf("%02X", b)
  256. }
  257. return strings.Join(hex, ":")
  258. }
  259. func (c *Client) dialTLS(network string, addr string) (net.Conn, error) {
  260. // Would be nice if there was a tls.Config.Verify func,
  261. // see tls.clientHandshakeState.doFullHandshake
  262. conn, err := tls.Dial(network, addr, c.t.TLSClientConfig)
  263. if err == nil {
  264. return conn, nil
  265. }
  266. switch err.(type) {
  267. case x509.UnknownAuthorityError:
  268. case x509.HostnameError:
  269. default:
  270. return nil, err
  271. }
  272. thumbprint := c.Thumbprint(addr)
  273. if thumbprint == "" {
  274. return nil, err
  275. }
  276. config := &tls.Config{InsecureSkipVerify: true}
  277. conn, err = tls.Dial(network, addr, config)
  278. if err != nil {
  279. return nil, err
  280. }
  281. cert := conn.ConnectionState().PeerCertificates[0]
  282. peer := ThumbprintSHA1(cert)
  283. if thumbprint != peer {
  284. _ = conn.Close()
  285. return nil, fmt.Errorf("Host %q thumbprint does not match %q", addr, thumbprint)
  286. }
  287. return conn, nil
  288. }
  289. // splitHostPort is similar to net.SplitHostPort,
  290. // but rather than return error if there isn't a ':port',
  291. // return an empty string for the port.
  292. func splitHostPort(host string) (string, string) {
  293. ix := strings.LastIndex(host, ":")
  294. if ix <= strings.LastIndex(host, "]") {
  295. return host, ""
  296. }
  297. name := host[:ix]
  298. port := host[ix+1:]
  299. return name, port
  300. }
  301. const sdkTunnel = "sdkTunnel:8089"
  302. func (c *Client) Certificate() *tls.Certificate {
  303. certs := c.t.TLSClientConfig.Certificates
  304. if len(certs) == 0 {
  305. return nil
  306. }
  307. return &certs[0]
  308. }
  309. func (c *Client) SetCertificate(cert tls.Certificate) {
  310. t := c.Client.Transport.(*http.Transport)
  311. // Extension or HoK certificate
  312. t.TLSClientConfig.Certificates = []tls.Certificate{cert}
  313. }
  314. // Tunnel returns a Client configured to proxy requests through vCenter's http port 80,
  315. // to the SDK tunnel virtual host. Use of the SDK tunnel is required by LoginExtensionByCertificate()
  316. // and optional for other methods.
  317. func (c *Client) Tunnel() *Client {
  318. tunnel := c.NewServiceClient(c.u.Path, c.Namespace)
  319. t := tunnel.Client.Transport.(*http.Transport)
  320. // Proxy to vCenter host on port 80
  321. host := tunnel.u.Hostname()
  322. // Should be no reason to change the default port other than testing
  323. key := "GOVMOMI_TUNNEL_PROXY_PORT"
  324. port := tunnel.URL().Query().Get(key)
  325. if port == "" {
  326. port = os.Getenv(key)
  327. }
  328. if port != "" {
  329. host += ":" + port
  330. }
  331. t.Proxy = http.ProxyURL(&url.URL{
  332. Scheme: "http",
  333. Host: host,
  334. })
  335. // Rewrite url Host to use the sdk tunnel, required for a certificate request.
  336. tunnel.u.Host = sdkTunnel
  337. return tunnel
  338. }
  339. func (c *Client) URL() *url.URL {
  340. urlCopy := *c.u
  341. return &urlCopy
  342. }
  343. type marshaledClient struct {
  344. Cookies []*http.Cookie
  345. URL *url.URL
  346. Insecure bool
  347. }
  348. func (c *Client) MarshalJSON() ([]byte, error) {
  349. m := marshaledClient{
  350. Cookies: c.Jar.Cookies(c.u),
  351. URL: c.u,
  352. Insecure: c.k,
  353. }
  354. return json.Marshal(m)
  355. }
  356. func (c *Client) UnmarshalJSON(b []byte) error {
  357. var m marshaledClient
  358. err := json.Unmarshal(b, &m)
  359. if err != nil {
  360. return err
  361. }
  362. *c = *NewClient(m.URL, m.Insecure)
  363. c.Jar.SetCookies(m.URL, m.Cookies)
  364. return nil
  365. }
  366. type kindContext struct{}
  367. func (c *Client) Do(ctx context.Context, req *http.Request, f func(*http.Response) error) error {
  368. if ctx == nil {
  369. ctx = context.Background()
  370. }
  371. // Create debugging context for this round trip
  372. d := c.d.newRoundTrip()
  373. if d.enabled() {
  374. defer d.done()
  375. }
  376. if c.UserAgent != "" {
  377. req.Header.Set(`User-Agent`, c.UserAgent)
  378. }
  379. if d.enabled() {
  380. d.debugRequest(req)
  381. }
  382. tstart := time.Now()
  383. res, err := c.Client.Do(req.WithContext(ctx))
  384. tstop := time.Now()
  385. if d.enabled() {
  386. var name string
  387. if kind, ok := ctx.Value(kindContext{}).(HasFault); ok {
  388. name = fmt.Sprintf("%T", kind)
  389. } else {
  390. name = fmt.Sprintf("%s %s", req.Method, req.URL)
  391. }
  392. d.logf("%6dms (%s)", tstop.Sub(tstart)/time.Millisecond, name)
  393. }
  394. if err != nil {
  395. return err
  396. }
  397. defer res.Body.Close()
  398. if d.enabled() {
  399. d.debugResponse(res)
  400. }
  401. return f(res)
  402. }
  403. // Signer can be implemented by soap.Header.Security to sign requests.
  404. // If the soap.Header.Security field is set to an implementation of Signer via WithHeader(),
  405. // then Client.RoundTrip will call Sign() to marshal the SOAP request.
  406. type Signer interface {
  407. Sign(Envelope) ([]byte, error)
  408. }
  409. type headerContext struct{}
  410. // WithHeader can be used to modify the outgoing request soap.Header fields.
  411. func (c *Client) WithHeader(ctx context.Context, header Header) context.Context {
  412. return context.WithValue(ctx, headerContext{}, header)
  413. }
  414. func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error {
  415. var err error
  416. var b []byte
  417. reqEnv := Envelope{Body: reqBody}
  418. resEnv := Envelope{Body: resBody}
  419. h, ok := ctx.Value(headerContext{}).(Header)
  420. if !ok {
  421. h = Header{}
  422. }
  423. // We added support for OperationID before soap.Header was exported.
  424. if id, ok := ctx.Value(types.ID{}).(string); ok {
  425. h.ID = id
  426. }
  427. h.Cookie = c.cookie
  428. if h.Cookie != "" || h.ID != "" || h.Security != nil {
  429. reqEnv.Header = &h // XML marshal header only if a field is set
  430. }
  431. if signer, ok := h.Security.(Signer); ok {
  432. b, err = signer.Sign(reqEnv)
  433. if err != nil {
  434. return err
  435. }
  436. } else {
  437. b, err = xml.Marshal(reqEnv)
  438. if err != nil {
  439. panic(err)
  440. }
  441. }
  442. rawReqBody := io.MultiReader(strings.NewReader(xml.Header), bytes.NewReader(b))
  443. req, err := http.NewRequest("POST", c.u.String(), rawReqBody)
  444. if err != nil {
  445. panic(err)
  446. }
  447. req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`)
  448. action := h.Action
  449. if action == "" {
  450. action = fmt.Sprintf("%s/%s", c.Namespace, c.Version)
  451. }
  452. req.Header.Set(`SOAPAction`, action)
  453. return c.Do(context.WithValue(ctx, kindContext{}, resBody), req, func(res *http.Response) error {
  454. switch res.StatusCode {
  455. case http.StatusOK:
  456. // OK
  457. case http.StatusInternalServerError:
  458. // Error, but typically includes a body explaining the error
  459. default:
  460. return errors.New(res.Status)
  461. }
  462. dec := xml.NewDecoder(res.Body)
  463. dec.TypeFunc = types.TypeFunc()
  464. err = dec.Decode(&resEnv)
  465. if err != nil {
  466. return err
  467. }
  468. if f := resBody.Fault(); f != nil {
  469. return WrapSoapFault(f)
  470. }
  471. return err
  472. })
  473. }
  474. func (c *Client) CloseIdleConnections() {
  475. c.t.CloseIdleConnections()
  476. }
  477. // ParseURL wraps url.Parse to rewrite the URL.Host field
  478. // In the case of VM guest uploads or NFC lease URLs, a Host
  479. // field with a value of "*" is rewritten to the Client's URL.Host.
  480. func (c *Client) ParseURL(urlStr string) (*url.URL, error) {
  481. u, err := url.Parse(urlStr)
  482. if err != nil {
  483. return nil, err
  484. }
  485. host, _ := splitHostPort(u.Host)
  486. if host == "*" {
  487. // Also use Client's port, to support port forwarding
  488. u.Host = c.URL().Host
  489. }
  490. return u, nil
  491. }
  492. type Upload struct {
  493. Type string
  494. Method string
  495. ContentLength int64
  496. Headers map[string]string
  497. Ticket *http.Cookie
  498. Progress progress.Sinker
  499. }
  500. var DefaultUpload = Upload{
  501. Type: "application/octet-stream",
  502. Method: "PUT",
  503. }
  504. // Upload PUTs the local file to the given URL
  505. func (c *Client) Upload(ctx context.Context, f io.Reader, u *url.URL, param *Upload) error {
  506. var err error
  507. if param.Progress != nil {
  508. pr := progress.NewReader(ctx, param.Progress, f, param.ContentLength)
  509. f = pr
  510. // Mark progress reader as done when returning from this function.
  511. defer func() {
  512. pr.Done(err)
  513. }()
  514. }
  515. req, err := http.NewRequest(param.Method, u.String(), f)
  516. if err != nil {
  517. return err
  518. }
  519. req = req.WithContext(ctx)
  520. req.ContentLength = param.ContentLength
  521. req.Header.Set("Content-Type", param.Type)
  522. for k, v := range param.Headers {
  523. req.Header.Add(k, v)
  524. }
  525. if param.Ticket != nil {
  526. req.AddCookie(param.Ticket)
  527. }
  528. res, err := c.Client.Do(req)
  529. if err != nil {
  530. return err
  531. }
  532. defer res.Body.Close()
  533. switch res.StatusCode {
  534. case http.StatusOK:
  535. case http.StatusCreated:
  536. default:
  537. err = errors.New(res.Status)
  538. }
  539. return err
  540. }
  541. // UploadFile PUTs the local file to the given URL
  542. func (c *Client) UploadFile(ctx context.Context, file string, u *url.URL, param *Upload) error {
  543. if param == nil {
  544. p := DefaultUpload // Copy since we set ContentLength
  545. param = &p
  546. }
  547. s, err := os.Stat(file)
  548. if err != nil {
  549. return err
  550. }
  551. f, err := os.Open(file)
  552. if err != nil {
  553. return err
  554. }
  555. defer f.Close()
  556. param.ContentLength = s.Size()
  557. return c.Upload(ctx, f, u, param)
  558. }
  559. type Download struct {
  560. Method string
  561. Headers map[string]string
  562. Ticket *http.Cookie
  563. Progress progress.Sinker
  564. Writer io.Writer
  565. }
  566. var DefaultDownload = Download{
  567. Method: "GET",
  568. }
  569. // DownloadRequest wraps http.Client.Do, returning the http.Response without checking its StatusCode
  570. func (c *Client) DownloadRequest(ctx context.Context, u *url.URL, param *Download) (*http.Response, error) {
  571. req, err := http.NewRequest(param.Method, u.String(), nil)
  572. if err != nil {
  573. return nil, err
  574. }
  575. req = req.WithContext(ctx)
  576. for k, v := range param.Headers {
  577. req.Header.Add(k, v)
  578. }
  579. if param.Ticket != nil {
  580. req.AddCookie(param.Ticket)
  581. }
  582. return c.Client.Do(req)
  583. }
  584. // Download GETs the remote file from the given URL
  585. func (c *Client) Download(ctx context.Context, u *url.URL, param *Download) (io.ReadCloser, int64, error) {
  586. res, err := c.DownloadRequest(ctx, u, param)
  587. if err != nil {
  588. return nil, 0, err
  589. }
  590. switch res.StatusCode {
  591. case http.StatusOK:
  592. default:
  593. err = errors.New(res.Status)
  594. }
  595. if err != nil {
  596. return nil, 0, err
  597. }
  598. r := res.Body
  599. return r, res.ContentLength, nil
  600. }
  601. func (c *Client) WriteFile(ctx context.Context, file string, src io.Reader, size int64, s progress.Sinker, w io.Writer) error {
  602. var err error
  603. r := src
  604. fh, err := os.Create(file)
  605. if err != nil {
  606. return err
  607. }
  608. if s != nil {
  609. pr := progress.NewReader(ctx, s, src, size)
  610. src = pr
  611. // Mark progress reader as done when returning from this function.
  612. defer func() {
  613. pr.Done(err)
  614. }()
  615. }
  616. if w == nil {
  617. w = fh
  618. } else {
  619. w = io.MultiWriter(w, fh)
  620. }
  621. _, err = io.Copy(w, r)
  622. cerr := fh.Close()
  623. if err == nil {
  624. err = cerr
  625. }
  626. return err
  627. }
  628. // DownloadFile GETs the given URL to a local file
  629. func (c *Client) DownloadFile(ctx context.Context, file string, u *url.URL, param *Download) error {
  630. var err error
  631. if param == nil {
  632. param = &DefaultDownload
  633. }
  634. rc, contentLength, err := c.Download(ctx, u, param)
  635. if err != nil {
  636. return err
  637. }
  638. return c.WriteFile(ctx, file, rc, contentLength, param.Progress, param.Writer)
  639. }