1# -*- coding: utf-8 -*- 2# vim:ts=4:sw=4:expandtab 3 4from __future__ import print_function, unicode_literals 5import os 6import re 7import six 8import pytest 9from muacrypt import mime 10from .test_account import gen_ac_mail_msg 11 12 13@pytest.fixture 14def account_maker(mycmd): 15 def account_maker(name, addr): 16 mycmd.run_ok(["add-account", "-a", name, "--email-regex=" + addr]) 17 acc = mycmd.get_account(name) 18 acc.addr = addr 19 return acc 20 return account_maker 21 22 23def test_help(cmd): 24 cmd.run_ok([], """ 25 *make-header* 26 *export-public-key* 27 *export-secret-key* 28 """) 29 cmd.run_ok(["--help"], """ 30 *access and manage* 31 """) 32 33 34def test_init_and_make_header(mycmd): 35 mycmd.run_fail(["make-header", "xyz"], """ 36 *AccountNotFound*xyz* 37 """) 38 adr = "x@yz.org" 39 mycmd.run_ok(["add-account", "--email-regex", adr]) 40 out = mycmd.run_ok(["make-header", adr]) 41 r = mime.parse_one_ac_header_from_string(out) 42 assert "prefer-encrypt" not in out 43 assert "type" not in out 44 assert r.addr == adr 45 out2 = mycmd.run_ok(["make-header", adr]) 46 assert out == out2 47 48 49def test_init_and_make_header_with_envvar(cmd, tmpdir): 50 with tmpdir.as_cwd(): 51 os.environ["MUACRYPT_BASEDIR"] = "." 52 test_init_and_make_header(cmd) 53 54 55def test_exports_and_status_plain(mycmd): 56 mycmd.run_ok(["add-account", "--email-regex=123@z.org"]) 57 out = mycmd.run_ok(["export-public-key"]) 58 check_ascii(out) 59 out = mycmd.run_ok(["export-secret-key"]) 60 check_ascii(out) 61 out = mycmd.run_ok(["status"], """ 62 account-dir:* 63 *account*default* 64 *prefer-encrypt*nopreference* 65 *own-keyhandle:* 66 """) 67 out = mycmd.run_ok(["status", "-v"], """ 68 account-dir:* 69 *account*default* 70 *prefer-encrypt*nopreference* 71 *own-keyhandle:* 72 """) 73 74 75def check_ascii(out): 76 if isinstance(out, six.text_type): 77 out.encode("ascii") 78 else: 79 out.decode("ascii") 80 81 82class TestProcessIncoming: 83 def test_process_incoming(self, mycmd, datadir): 84 mycmd.run_ok(["add-account", "-a", "account1", "--email-regex=some@example.org"]) 85 mail = datadir.read("rsa2048-simple.eml") 86 mycmd.run_fail(["process-incoming"], """ 87 *AccountNotFound*bob@testsuite.autocrypt.org* 88 """, input=mail) 89 90 msg = mime.parse_message_from_string(mail) 91 msg.replace_header("Delivered-To", "some@example.org") 92 newmail = msg.as_string() 93 out = mycmd.run_ok(["process-incoming"], """ 94 *processed*account*account1* 95 """, input=newmail) 96 97 # now export the public key 98 m = re.search(r'key=(\w+)', out) 99 keyhandle, = m.groups() 100 mycmd.run_ok(["export-public-key", "--account=account1", keyhandle]) 101 mycmd.run_ok(["status"]) 102 103 def test_process_incoming_no_autocrypt(self, mycmd): 104 mycmd.run_ok(["add-account", "--email-regex=b@b.org"]) 105 mycmd.run_ok(["peerstate", "a@a.org"]) 106 msg = mime.gen_mail_msg(From="Alice <a@a.org>", To=["b@b.org"], _dto=True) 107 mycmd.run_ok(["process-incoming"], """ 108 *processed*default*no*Autocrypt*header* 109 """, input=msg.as_string()) 110 mycmd.run_ok(["peerstate", "a@a.org"]) 111 112 def test_peerstate_with_ac_keys(self, mycmd, account_maker): 113 acc1 = account_maker("acc1", "a@a.org") 114 acc2 = account_maker("acc2", "b@b.org") 115 acc2.process_incoming(gen_ac_mail_msg(acc1, acc2)) 116 mycmd.run_ok(["peerstate", "-a", "acc2", "a@a.org"]) 117 acc1.process_incoming(gen_ac_mail_msg(acc2, acc1)) 118 mycmd.run_ok(["peerstate", "-a", "acc1", "b@b.org"]) 119 120 def test_twice(self, mycmd, account_maker, linematch): 121 acc1 = account_maker("acc1", "a@a.org") 122 acc2 = account_maker("acc2", "b@b.org") 123 msg = gen_ac_mail_msg(acc1, acc2) 124 mycmd.run_ok(["process-incoming", "-a", "acc2"], input=msg.as_string()) 125 out = mycmd.run_ok(["process-incoming", "-a", "acc2"], input=msg.as_string()) 126 linematch(out, """ 127 *already known* 128 """) 129 out = mycmd.run_ok(["process-incoming", "-a", "acc2", "--reparse"], input=msg.as_string()) 130 linematch(out, """ 131 *processed*found* 132 """) 133 134 135class TestScandir: 136 def test_scandir_incoming_ac(self, mycmd, account_maker, tmpdir): 137 acc1 = account_maker("account1", "acc1@x.org") 138 acc2 = account_maker("account2", "acc2@x.org") 139 140 maildir = tmpdir.ensure("maildir", dir=True) 141 msg = gen_ac_mail_msg(acc1, acc2, _dto=True) 142 maildir.join("msg1").write(msg.as_string()) 143 144 peerstate = acc2.get_peerstate("acc1@x.org") 145 assert not peerstate.has_direct_key() 146 mycmd.run_ok(["scandir-incoming", str(maildir)]) 147 peerstate = acc2.get_peerstate("acc1@x.org") 148 assert peerstate.has_direct_key() 149 150 def test_scandir_incoming_ac_twice(self, mycmd, account_maker, tmpdir, linematch): 151 acc1 = account_maker("account1", "acc1@x.org") 152 acc2 = account_maker("account2", "acc2@x.org") 153 154 maildir = tmpdir.ensure("maildir", dir=True) 155 msg = gen_ac_mail_msg(acc1, acc2, _dto=True) 156 maildir.join("msg1").write(msg.as_string()) 157 msg2 = gen_ac_mail_msg(acc1, acc2, _dto=True) 158 maildir.join("msg2").write(msg2.as_string()) 159 mycmd.run_ok(["scandir-incoming", str(maildir)]) 160 peerstate = acc2.get_peerstate("acc1@x.org") 161 assert peerstate.has_direct_key() 162 out = mycmd.run_ok(["scandir-incoming", str(maildir)]) 163 linematch(out, """ 164 *already known* 165 """) 166 out = mycmd.run_ok(["scandir-incoming", "--reparse", str(maildir)]) 167 linematch(out, """ 168 *found Autocrypt* 169 """) 170 171 172class TestAccountCommands: 173 def test_add_list_del_account(self, mycmd): 174 mycmd.run_ok(["status"], """ 175 *no accounts configured* 176 """) 177 mycmd.run_ok(["add-account", "--email-regex=home@example.org"], """ 178 *account added*default* 179 """) 180 mycmd.run_ok(["status"], """ 181 *account*default* 182 *home@example.org* 183 """) 184 mycmd.run_ok(["del-account"]) 185 mycmd.run_ok(["status"], """ 186 *no accounts configured* 187 """) 188 189 def test_add_two_accounts_requires_option(self, mycmd): 190 mycmd.run_ok(["add-account"], """ 191 *account added*default* 192 """) 193 mycmd.run_fail(["add-account"], """ 194 AccountExists*default* 195 """) 196 197 def test_modify_account_prefer_encrypt(self, mycmd): 198 mycmd.run_ok(["add-account"]) 199 mycmd.run_ok(["status"], """ 200 *account*default* 201 """) 202 mycmd.run_ok(["mod-account", "--prefer-encrypt=mutual"], """ 203 *account modified*default* 204 *email?regex*.** 205 *prefer-encrypt*mutual* 206 """) 207 mycmd.run_ok(["mod-account", "--email-regex=xyz"], """ 208 *account modified*default* 209 *email?regex*xyz* 210 *prefer-encrypt*mutual* 211 """) 212 213 mycmd.run_ok(["mod-account", "--prefer-encrypt=nopreference"], """ 214 *account modified*default* 215 *email?regex*xyz* 216 *prefer-encrypt*nopreference* 217 """) 218 219 def test_init_existing_key_native_gpg(self, mycmd, monkeypatch, bingpg, gpgpath): 220 adr = "x@y.org" 221 keyhandle = bingpg.gen_secret_key(adr) 222 monkeypatch.setenv("GNUPGHOME", bingpg.homedir) 223 mycmd.run_ok(["add-account", "--use-key", adr, 224 "--gpgbin=%s" % gpgpath, "--use-system-keyring"], """ 225 *gpgmode*system* 226 *gpgbin*{}* 227 *own-keyhandle*{}* 228 """.format(gpgpath, keyhandle)) 229 mycmd.run_ok(["make-header", adr], """ 230 *Autocrypt*addr=x@y.org* 231 """) 232 233 def test_test_email(self, mycmd): 234 mycmd.run_ok(["add-account", "--email-regex=(home|office)@example.org"]) 235 mycmd.run_ok(["find-account", "home@example.org"]) 236 mycmd.run_ok(["find-account", "office@example.org"]) 237 mycmd.run_fail(["find-account", "xhome@example.org"], """ 238 *AccountNotFound*xhome@example.org* 239 """) 240 241 242class TestProcessOutgoing: 243 244 def test_simple(self, mycmd, gen_mail): 245 mycmd.run_ok(["add-account"]) 246 mail = gen_mail() 247 out1 = mycmd.run_ok(["process-outgoing"], input=mail.as_string()) 248 m = mime.parse_message_from_string(out1) 249 assert len(m.get_all("Autocrypt")) == 1 250 found_header = "Autocrypt: " + m["Autocrypt"] 251 gen_header = mycmd.run_ok(["make-header", "a@a.org"]) 252 x1 = mime.parse_one_ac_header_from_string(gen_header) 253 x2 = mime.parse_one_ac_header_from_string(found_header) 254 assert x1 == x2 255 256 def test_matching_account(self, mycmd, gen_mail): 257 mycmd.run_ok(["add-account", "--email-regex=account1@a.org"]) 258 mail = gen_mail(From="x@y.org") 259 # mycmd.run_fail(["process-outgoing"], input=mail.as_string(), fnl=""" 260 # *AccountNotFound*x@y.org* 261 # """) 262 out0 = mycmd.run_fail(["process-outgoing"], input=mail.as_string()) 263 assert "Autocrypt" not in out0 264 265 mail = gen_mail(From="account1@a.org") 266 out1 = mycmd.run_ok(["process-outgoing"], input=mail.as_string()) 267 msg2 = mime.parse_message_from_string(out1) 268 assert "account1@a.org" in msg2["Autocrypt"] 269 270 def test_simple_dont_replace(self, mycmd, gen_mail): 271 mycmd.run_ok(["add-account"]) 272 mail = gen_mail() 273 gen_header = mycmd.run_ok(["make-header", "x@x.org"]) 274 mail.add_header("Autocrypt", gen_header) 275 276 out1 = mycmd.run_ok(["process-outgoing"], input=mail.as_string()) 277 m = mime.parse_message_from_string(out1) 278 assert len(m.get_all("Autocrypt")) == 1 279 x1 = mime.parse_ac_headervalue(m["Autocrypt"]) 280 x2 = mime.parse_ac_headervalue(gen_header) 281 assert x1 == x2 282 283 @pytest.mark.parametrize("addr", ["a@a.org", "ño@example.org"]) 284 def test_sendmail(self, mycmd, gen_mail, popen_mock, addr): 285 mycmd.run_ok(["add-account"]) 286 mail = gen_mail().as_string() 287 pargs = ["-oi", addr] 288 mycmd.run_ok(["sendmail", "-f", "--"] + pargs, input=mail) 289 assert len(popen_mock.calls) == 1 290 call = popen_mock.pop_next_call() 291 for x in pargs: 292 assert x in call.args 293 # make sure unknown option is passed to pipe 294 assert "-f" in call.args 295 out_msg = mime.parse_message_from_string(call.input.decode("utf8")) 296 assert "Autocrypt" in out_msg, out_msg.as_string() 297 298 def test_sendmail_no_account(self, mycmd, gen_mail, popen_mock): 299 mycmd.run_ok(["add-account", "--email-regex=account1@a.org"]) 300 mycmd.run_ok(["mod-account", "--email-regex", "123123"]) 301 mail = gen_mail().as_string() 302 pargs = ["-oi", "b@b.org"] 303 mycmd.run_fail(["sendmail", "-f", "--"] + pargs, input=mail) 304 # assert len(popen_mock.calls) == 1 305 # call = popen_mock.pop_next_call() 306 # for x in pargs: 307 # assert x in call.args 308 # # make sure unknown option is passed to pipe 309 # assert "-f" in call.args 310 # out_msg = mime.parse_message_from_string(call.input) 311 # assert "Autocrypt" not in out_msg, out_msg.as_string() 312 313 def test_sendmail_fails(self, mycmd, gen_mail, popen_mock): 314 mycmd.run_ok(["add-account", "--email-regex=.*"]) 315 mail = gen_mail().as_string() 316 pargs = ["-oi", "b@b.org"] 317 popen_mock.mock_next_call(ret=2) 318 mycmd.run_fail(["sendmail", "-f", "--", "--qwe"] + pargs, input=mail, code=2) 319 assert len(popen_mock.calls) == 1 320 call = popen_mock.pop_next_call() 321 for x in pargs: 322 assert x in call.args 323 # make sure unknown option is passed to pipe 324 assert "-f" in call.args 325 assert "--qwe" in call.args 326 327 def test_import_keydata(self, mycmd, datadir): 328 mycmd.run_ok(["add-account"]) 329 keydata = datadir.read_bytes("test1_autocrypt_org.key") 330 mycmd.run_ok(["import-public-key"], input=keydata) 331 out = mycmd.run_ok(["recommend", "test1@autocrypt.org"]) 332 assert "available" in out 333 334 mycmd.run_ok(["mod-account", "--prefer-encrypt", "mutual"]) 335 out = mycmd.run_ok(["import-public-key", "--prefer-encrypt=mutual"], input=keydata) 336 assert "imported" in out 337 out = mycmd.run_ok(["recommend", "test1@autocrypt.org"]) 338 assert "encrypt" in out 339 340 mycmd.run_ok(["import-public-key", "--prefer-encrypt=nopreference"], input=keydata) 341 out = mycmd.run_ok(["recommend", "test1@autocrypt.org"]) 342 assert "available" in out 343 344 345class TestRecommendation: 346 def test_recommend_empty(self, mycmd): 347 mycmd.run_ok(["add-account", "-a", "home"]) 348 mycmd.run_ok(["recommend", "-a", "home", "unknown@email.org"]) 349 350 def test_recommend_one(self, mycmd): 351 addr1 = "a@a.org" 352 addr2 = "b@b.org" 353 mycmd.run_ok(["add-account", "-a", "ac1", "--email-regex", addr1]) 354 mycmd.run_ok(["add-account", "-a", "ac2", "--email-regex", addr2]) 355 356 mycmd.send_mail(addr2, [addr1], Date=0) 357 assert "available" == mycmd.parse_recommendation("ac1", [addr2]) 358 359 # switch addr2 and addr1 prefer_encrypt to "mutual", send a mail 360 mycmd.run_ok(["mod-account", "-a", "ac1", "--prefer-encrypt", "mutual"]) 361 mycmd.run_ok(["mod-account", "-a", "ac2", "--prefer-encrypt", "mutual"]) 362 mycmd.send_mail(addr2, [addr1], Date=1) 363 assert "encrypt" == mycmd.parse_recommendation("ac1", [addr2]) 364 365 # send a non-ac mail and ask recommend again 366 mycmd.send_mail(addr2, [addr1], ac=False, Date=2) 367 assert "available" == mycmd.parse_recommendation("ac1", [addr2]) 368 369 def test_recommend_two(self, mycmd): 370 addrs = [] 371 for i in range(1, 4): 372 addr = "%d@x.org" % i 373 mycmd.run_ok(["add-account", "-a", "ac%d" % i, "--email-regex", addr]) 374 addrs.append(addr) 375 addr1, addr2, addr3 = addrs 376 377 mycmd.send_mail(addr2, [addr1]) 378 mycmd.send_mail(addr3, [addr1]) 379 assert "available" == mycmd.parse_recommendation("ac1", [addr2, addr2]) 380 381 # switch all accounts to mutual 382 for name in "ac1 ac2 ac2".split(): 383 mycmd.run_ok(["mod-account", "-a", name, "--prefer-encrypt", "mutual"]) 384 mycmd.send_mail(addr2, [addr1]) 385 mycmd.send_mail(addr3, [addr1]) 386 assert "encrypt" == mycmd.parse_recommendation("ac1", [addr2, addr2]) 387