summaryrefslogtreecommitdiff
path: root/misc/ios/detect.go
blob: d32bcc3202a443a26a7b2a394342d18e5feeb650 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// detect attempts to autodetect the correct
// values of the environment variables
// used by go_ios_exec.
// detect shells out to ideviceinfo, a third party program that can
// be obtained by following the instructions at
// https://github.com/libimobiledevice/libimobiledevice.
package main

import (
	"bytes"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

func main() {
	udids := getLines(exec.Command("idevice_id", "-l"))
	if len(udids) == 0 {
		fail("no udid found; is a device connected?")
	}

	mps := detectMobileProvisionFiles(udids)
	if len(mps) == 0 {
		fail("did not find mobile provision matching device udids %q", udids)
	}

	fmt.Println("# Available provisioning profiles below.")
	fmt.Println("# NOTE: Any existing app on the device with the app id specified by GOIOS_APP_ID")
	fmt.Println("# will be overwritten when running Go programs.")
	for _, mp := range mps {
		fmt.Println()
		f, err := ioutil.TempFile("", "go_ios_detect_")
		check(err)
		fname := f.Name()
		defer os.Remove(fname)

		out := output(parseMobileProvision(mp))
		_, err = f.Write(out)
		check(err)
		check(f.Close())

		cert, err := plistExtract(fname, "DeveloperCertificates:0")
		check(err)
		pcert, err := x509.ParseCertificate(cert)
		check(err)
		fmt.Printf("export GOIOS_DEV_ID=\"%s\"\n", pcert.Subject.CommonName)

		appID, err := plistExtract(fname, "Entitlements:application-identifier")
		check(err)
		fmt.Printf("export GOIOS_APP_ID=%s\n", appID)

		teamID, err := plistExtract(fname, "Entitlements:com.apple.developer.team-identifier")
		check(err)
		fmt.Printf("export GOIOS_TEAM_ID=%s\n", teamID)
	}
}

func detectMobileProvisionFiles(udids [][]byte) []string {
	cmd := exec.Command("mdfind", "-name", ".mobileprovision")
	lines := getLines(cmd)

	var files []string
	for _, line := range lines {
		if len(line) == 0 {
			continue
		}
		xmlLines := getLines(parseMobileProvision(string(line)))
		matches := 0
		for _, udid := range udids {
			for _, xmlLine := range xmlLines {
				if bytes.Contains(xmlLine, udid) {
					matches++
				}
			}
		}
		if matches == len(udids) {
			files = append(files, string(line))
		}
	}
	return files
}

func parseMobileProvision(fname string) *exec.Cmd {
	return exec.Command("security", "cms", "-D", "-i", string(fname))
}

func plistExtract(fname string, path string) ([]byte, error) {
	out, err := exec.Command("/usr/libexec/PlistBuddy", "-c", "Print "+path, fname).CombinedOutput()
	if err != nil {
		return nil, err
	}
	return bytes.TrimSpace(out), nil
}

func getLines(cmd *exec.Cmd) [][]byte {
	out := output(cmd)
	lines := bytes.Split(out, []byte("\n"))
	// Skip the empty line at the end.
	if len(lines[len(lines)-1]) == 0 {
		lines = lines[:len(lines)-1]
	}
	return lines
}

func output(cmd *exec.Cmd) []byte {
	out, err := cmd.Output()
	if err != nil {
		fmt.Println(strings.Join(cmd.Args, "\n"))
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	return out
}

func check(err error) {
	if err != nil {
		fail(err.Error())
	}
}

func fail(msg string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, msg, v...)
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}