/ src / namecoin.py.bak
namecoin.py.bak
  1  """
  2  Namecoin queries
  3  """
  4  # pylint: disable=too-many-branches,protected-access
  5  
  6  import base64
  7  import json
  8  import os
  9  import socket
 10  import sys
 11  
 12  from six.moves import http_client as httplib
 13  
 14  import defaults
 15  from addresses import decodeAddress
 16  from bmconfigparser import config
 17  from debug import logger
 18  from tr import _translate  # translate
 19  
 20  configSection = "bitmessagesettings"
 21  
 22  
 23  class RPCError(Exception):
 24      """Error thrown when the RPC call returns an error."""
 25  
 26      error = None
 27  
 28      def __init__(self, data):
 29          super(RPCError, self).__init__()
 30          self.error = data
 31  
 32      def __str__(self):
 33          return "{0}: {1}".format(type(self).__name__, self.error)
 34  
 35  
 36  class namecoinConnection(object):
 37      """This class handles the Namecoin identity integration."""
 38  
 39      user = None
 40      password = None
 41      host = None
 42      port = None
 43      nmctype = None
 44      bufsize = 4096
 45      queryid = 1
 46      con = None
 47  
 48      def __init__(self, options=None):
 49          """
 50          Initialise.  If options are given, take the connection settings from
 51          them instead of loading from the configs.  This can be used to test
 52          currently entered connection settings in the config dialog without
 53          actually changing the values (yet).
 54          """
 55          if options is None:
 56              self.nmctype = config.get(
 57                  configSection, "namecoinrpctype")
 58              self.host = config.get(
 59                  configSection, "namecoinrpchost")
 60              self.port = int(config.get(
 61                  configSection, "namecoinrpcport"))
 62              self.user = config.get(
 63                  configSection, "namecoinrpcuser")
 64              self.password = config.get(
 65                  configSection, "namecoinrpcpassword")
 66          else:
 67              self.nmctype = options["type"]
 68              self.host = options["host"]
 69              self.port = int(options["port"])
 70              self.user = options["user"]
 71              self.password = options["password"]
 72  
 73          assert self.nmctype == "namecoind" or self.nmctype == "nmcontrol"
 74          if self.nmctype == "namecoind":
 75              self.con = httplib.HTTPConnection(self.host, self.port, timeout=3)
 76  
 77      def query(self, identity):
 78          """
 79          Query for the bitmessage address corresponding to the given identity
 80          string.  If it doesn't contain a slash, id/ is prepended.  We return
 81          the result as (Error, Address) pair, where the Error is an error
 82          message to display or None in case of success.
 83          """
 84          slashPos = identity.find("/")
 85          if slashPos < 0:
 86              display_name = identity
 87              identity = "id/" + identity
 88          else:
 89              display_name = identity.split("/")[1]
 90  
 91          try:
 92              if self.nmctype == "namecoind":
 93                  res = self.callRPC("name_show", [identity])
 94                  res = res["value"]
 95              elif self.nmctype == "nmcontrol":
 96                  res = self.callRPC("data", ["getValue", identity])
 97                  res = res["reply"]
 98                  if not res:
 99                      return (_translate(
100                          "MainWindow", "The name %1 was not found."
101                      ).arg(identity.decode("utf-8", "ignore")), None)
102              else:
103                  assert False
104          except RPCError as exc:
105              logger.exception("Namecoin query RPC exception")
106              if isinstance(exc.error, dict):
107                  errmsg = exc.error["message"]
108              else:
109                  errmsg = exc.error
110              return (_translate(
111                  "MainWindow", "The namecoin query failed (%1)"
112              ).arg(errmsg.decode("utf-8", "ignore")), None)
113          except AssertionError:
114              return (_translate(
115                  "MainWindow", "Unknown namecoin interface type: %1"
116              ).arg(self.nmctype.decode("utf-8", "ignore")), None)
117          except Exception:
118              logger.exception("Namecoin query exception")
119              return (_translate(
120                  "MainWindow", "The namecoin query failed."), None)
121  
122          try:
123              res = json.loads(res)
124          except ValueError:
125              pass
126          else:
127              try:
128                  display_name = res["name"]
129              except KeyError:
130                  pass
131              res = res.get("bitmessage")
132  
133          valid = decodeAddress(res)[0] == "success"
134          return (
135              None, "%s <%s>" % (display_name, res)
136          ) if valid else (
137              _translate(
138                  "MainWindow",
139                  "The name %1 has no associated Bitmessage address."
140              ).arg(identity.decode("utf-8", "ignore")), None)
141  
142      def test(self):
143          """
144          Test the connection settings.  This routine tries to query a "getinfo"
145          command, and builds either an error message or a success message with
146          some info from it.
147          """
148          try:
149              if self.nmctype == "namecoind":
150                  try:
151                      vers = self.callRPC("getinfo", [])["version"]
152                  except RPCError:
153                      vers = self.callRPC("getnetworkinfo", [])["version"]
154  
155                  v3 = vers % 100
156                  vers = vers / 100
157                  v2 = vers % 100
158                  vers = vers / 100
159                  v1 = vers
160                  if v3 == 0:
161                      versStr = "0.%d.%d" % (v1, v2)
162                  else:
163                      versStr = "0.%d.%d.%d" % (v1, v2, v3)
164                  message = (
165                      "success",
166                      _translate(
167                          "MainWindow",
168                          "Success!  Namecoind version %1 running.").arg(
169                              versStr.decode("utf-8", "ignore")))
170  
171              elif self.nmctype == "nmcontrol":
172                  res = self.callRPC("data", ["status"])
173                  prefix = "Plugin data running"
174                  if ("reply" in res) and res["reply"][:len(prefix)] == prefix:
175                      return (
176                          "success",
177                          _translate(
178                              "MainWindow",
179                              "Success!  NMControll is up and running."
180                          )
181                      )
182  
183                  logger.error("Unexpected nmcontrol reply: %s", res)
184                  message = (
185                      "failed",
186                      _translate(
187                          "MainWindow",
188                          "Couldn\'t understand NMControl."
189                      )
190                  )
191  
192              else:
193                  sys.exit("Unsupported Namecoin type")
194  
195              return message
196  
197          except Exception:
198              logger.info("Namecoin connection test failure")
199              return (
200                  "failed",
201                  _translate(
202                      "MainWindow", "The connection to namecoin failed.")
203              )
204  
205      def callRPC(self, method, params):
206          """Helper routine that actually performs an JSON RPC call."""
207  
208          data = {"method": method, "params": params, "id": self.queryid}
209          if self.nmctype == "namecoind":
210              resp = self.queryHTTP(json.dumps(data))
211          elif self.nmctype == "nmcontrol":
212              resp = self.queryServer(json.dumps(data))
213          else:
214              assert False
215          val = json.loads(resp)
216  
217          if val["id"] != self.queryid:
218              raise Exception("ID mismatch in JSON RPC answer.")
219  
220          if self.nmctype == "namecoind":
221              self.queryid = self.queryid + 1
222  
223          error = val["error"]
224          if error is None:
225              return val["result"]
226  
227          if isinstance(error, bool):
228              raise RPCError(val["result"])
229          raise RPCError(error)
230  
231      def queryHTTP(self, data):
232          """Query the server via HTTP."""
233  
234          result = None
235  
236          try:
237              self.con.putrequest("POST", "/")
238              self.con.putheader("Connection", "Keep-Alive")
239              self.con.putheader("User-Agent", "bitmessage")
240              self.con.putheader("Host", self.host)
241              self.con.putheader("Content-Type", "application/json")
242              self.con.putheader("Content-Length", str(len(data)))
243              self.con.putheader("Accept", "application/json")
244              authstr = "%s:%s" % (self.user, self.password)
245              self.con.putheader(
246                  "Authorization", "Basic %s" % base64.b64encode(authstr))
247              self.con.endheaders()
248              self.con.send(data)
249          except:  # noqa:E722
250              logger.info("HTTP connection error")
251              return None
252  
253          try:
254              resp = self.con.getresponse()
255              result = resp.read()
256              if resp.status != 200:
257                  raise Exception(
258                      "Namecoin returned status"
259                      " %i: %s" % (resp.status, resp.reason))
260          except:  # noqa:E722
261              logger.info("HTTP receive error")
262              return None
263  
264          return result
265  
266      def queryServer(self, data):
267          """Helper routine sending data to the RPC "
268          "server and returning the result."""
269  
270          try:
271              s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
272              s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
273              s.settimeout(3)
274              s.connect((self.host, self.port))
275              s.sendall(data)
276              result = ""
277  
278              while True:
279                  tmp = s.recv(self.bufsize)
280                  if not tmp:
281                      break
282                  result += tmp
283  
284              s.close()
285  
286              return result
287  
288          except socket.error as exc:
289              raise Exception("Socket error in RPC connection: %s" % exc)
290  
291  
292  def lookupNamecoinFolder():
293      """
294      Look up the namecoin data folder.
295  
296      .. todo:: Check whether this works on other platforms as well!
297      """
298  
299      app = "namecoin"
300      from os import environ, path
301      if sys.platform == "darwin":
302          if "HOME" in environ:
303              dataFolder = path.join(os.environ["HOME"],
304                                     "Library/Application Support/", app) + "/"
305          else:
306              sys.exit(
307                  "Could not find home folder, please report this message"
308                  " and your OS X version to the BitMessage Github."
309              )
310  
311      elif "win32" in sys.platform or "win64" in sys.platform:
312          dataFolder = path.join(environ["APPDATA"], app) + "\\"
313      else:
314          dataFolder = path.join(environ["HOME"], ".%s" % app) + "/"
315  
316      return dataFolder
317  
318  
319  def ensureNamecoinOptions():
320      """
321      Ensure all namecoin options are set, by setting those to default values
322      that aren't there.
323      """
324  
325      if not config.has_option(configSection, "namecoinrpctype"):
326          config.set(configSection, "namecoinrpctype", "namecoind")
327      if not config.has_option(configSection, "namecoinrpchost"):
328          config.set(configSection, "namecoinrpchost", "localhost")
329  
330      hasUser = config.has_option(configSection, "namecoinrpcuser")
331      hasPass = config.has_option(configSection, "namecoinrpcpassword")
332      hasPort = config.has_option(configSection, "namecoinrpcport")
333  
334      # Try to read user/password from .namecoin configuration file.
335      defaultUser = ""
336      defaultPass = ""
337      nmcFolder = lookupNamecoinFolder()
338      nmcConfig = nmcFolder + "namecoin.conf"
339      try:
340          nmc = open(nmcConfig, "r")
341  
342          while True:
343              line = nmc.readline()
344              if line == "":
345                  break
346              parts = line.split("=")
347              if len(parts) == 2:
348                  key = parts[0]
349                  val = parts[1].rstrip()
350  
351                  if key == "rpcuser" and not hasUser:
352                      defaultUser = val
353                  if key == "rpcpassword" and not hasPass:
354                      defaultPass = val
355                  if key == "rpcport":
356                      defaults.namecoinDefaultRpcPort = val
357  
358          nmc.close()
359      except IOError:
360          logger.warning(
361              "%s unreadable or missing, Namecoin support deactivated",
362              nmcConfig)
363      except Exception:
364          logger.warning("Error processing namecoin.conf", exc_info=True)
365  
366      # If still nothing found, set empty at least.
367      if not hasUser:
368          config.set(configSection, "namecoinrpcuser", defaultUser)
369      if not hasPass:
370          config.set(configSection, "namecoinrpcpassword", defaultPass)
371  
372      # Set default port now, possibly to found value.
373      if not hasPort:
374          config.set(configSection, "namecoinrpcport", defaults.namecoinDefaultRpcPort)