main.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "flag"
20 "fmt"
21 "html/template"
22 golog "log"
23 "log/syslog"
24 notrand "math/rand"
25 "os"
26 "runtime/pprof"
27 "strconv"
28 "time"
29
30 "humungus.tedunangst.com/r/webs/log"
31)
32
33var softwareVersion = "develop"
34
35func init() {
36 notrand.Seed(time.Now().Unix())
37}
38
39var serverName string
40var serverPrefix string
41var masqName string
42var dataDir = "."
43var viewDir = "."
44var iconName = "icon.png"
45var serverMsg template.HTML
46var aboutMsg template.HTML
47var loginMsg template.HTML
48
49func serverURL(u string, args ...interface{}) string {
50 return fmt.Sprintf("https://"+serverName+u, args...)
51}
52
53func ElaborateUnitTests() {
54}
55
56func unplugserver(hostname string) {
57 db := opendatabase()
58 xid := fmt.Sprintf("https://%s", hostname)
59 db.Exec("delete from honkers where xid = ? and flavor = 'dub'", xid)
60 db.Exec("delete from doovers where rcpt = ?", xid)
61 xid += "/%"
62 db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
63 db.Exec("delete from doovers where rcpt like ?", xid)
64}
65
66func reexecArgs(cmd string) []string {
67 args := []string{"-datadir", dataDir}
68 args = append(args, log.Args()...)
69 args = append(args, cmd)
70 return args
71}
72
73var elog, ilog, dlog *golog.Logger
74
75func errx(msg string, args ...interface{}) {
76 fmt.Fprintf(os.Stderr, msg+"\n", args...)
77 os.Exit(1)
78}
79
80var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
81var memprofile = flag.String("memprofile", "", "write memory profile to this file")
82var memprofilefd *os.File
83
84func main() {
85 flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
86 flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
87 flag.Parse()
88 if *cpuprofile != "" {
89 f, err := os.Create(*cpuprofile)
90 if err != nil {
91 errx("can't open cpu profile: %s", err)
92 }
93 pprof.StartCPUProfile(f)
94 }
95 if *memprofile != "" {
96 f, err := os.Create(*memprofile)
97 if err != nil {
98 errx("can't open mem profile: %s", err)
99 }
100 memprofilefd = f
101 }
102
103 log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
104 elog = log.E
105 ilog = log.I
106 dlog = log.D
107
108 if os.Geteuid() == 0 {
109 elog.Fatalf("do not run honk as root")
110 }
111
112 args := flag.Args()
113 cmd := "run"
114 if len(args) > 0 {
115 cmd = args[0]
116 }
117 switch cmd {
118 case "init":
119 initdb()
120 case "upgrade":
121 upgradedb()
122 case "version":
123 fmt.Println(softwareVersion)
124 os.Exit(0)
125 }
126 db := opendatabase()
127 dbversion := 0
128 getconfig("dbversion", &dbversion)
129 if dbversion != myVersion {
130 elog.Fatal("incorrect database version. run upgrade.")
131 }
132 getconfig("servermsg", &serverMsg)
133 getconfig("aboutmsg", &aboutMsg)
134 getconfig("loginmsg", &loginMsg)
135 getconfig("servername", &serverName)
136 getconfig("masqname", &masqName)
137 if masqName == "" {
138 masqName = serverName
139 }
140 serverPrefix = fmt.Sprintf("https://%s/", serverName)
141 getconfig("usersep", &userSep)
142 getconfig("honksep", &honkSep)
143 getconfig("devel", &develMode)
144 if develMode {
145 gogglesDoNothing()
146 }
147 getconfig("fasttimeout", &fastTimeout)
148 getconfig("slowtimeout", &slowTimeout)
149 getconfig("honkwindow", &honkwindow)
150 honkwindow *= 24 * time.Hour
151
152 prepareStatements(db)
153
154 switch cmd {
155 case "admin":
156 adminscreen()
157 case "import":
158 if len(args) != 4 {
159 errx("import username honk|mastodon|twitter srcdir")
160 }
161 importMain(args[1], args[2], args[3])
162 case "export":
163 if len(args) != 3 {
164 errx("export username destdir")
165 }
166 export(args[1], args[2])
167 case "devel":
168 if len(args) != 2 {
169 errx("need an argument: devel (on|off)")
170 }
171 switch args[1] {
172 case "on":
173 setconfig("devel", 1)
174 case "off":
175 setconfig("devel", 0)
176 default:
177 errx("argument must be on or off")
178 }
179 case "setconfig":
180 if len(args) != 3 {
181 errx("need an argument: setconfig key val")
182 }
183 var val interface{}
184 var err error
185 if val, err = strconv.Atoi(args[2]); err != nil {
186 val = args[2]
187 }
188 setconfig(args[1], val)
189 case "adduser":
190 adduser()
191 case "deluser":
192 if len(args) < 2 {
193 errx("usage: honk deluser username")
194 }
195 deluser(args[1])
196 case "chpass":
197 if len(args) < 2 {
198 errx("usage: honk chpass username")
199 }
200 chpass(args[1])
201 case "follow":
202 if len(args) < 3 {
203 errx("usage: honk follow username url")
204 }
205 user, err := butwhatabout(args[1])
206 if err != nil {
207 errx("user %s not found", args[1])
208 }
209 var meta HonkerMeta
210 mj, _ := jsonify(&meta)
211 honkerid, err := savehonker(user, args[2], "", "presub", "", mj)
212 if err != nil {
213 errx("had some trouble with that: %s", err)
214 }
215 followyou(user, honkerid, true)
216 case "unfollow":
217 if len(args) < 3 {
218 errx("usage: honk unfollow username url")
219 }
220 user, err := butwhatabout(args[1])
221 if err != nil {
222 errx("user not found")
223 }
224 row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
225 var honkerid int64
226 err = row.Scan(&honkerid)
227 if err != nil {
228 errx("sorry couldn't find them")
229 }
230 unfollowyou(user, honkerid, true)
231 case "sendmsg":
232 if len(args) < 4 {
233 errx("usage: honk send username filename rcpt")
234 }
235 user, err := butwhatabout(args[1])
236 if err != nil {
237 errx("user %s not found", args[1])
238 }
239 data, err := os.ReadFile(args[2])
240 if err != nil {
241 errx("can't read file: %s", err)
242 }
243 deliverate(user.ID, args[3], data)
244 case "cleanup":
245 arg := "30"
246 if len(args) > 1 {
247 arg = args[1]
248 }
249 cleanupdb(arg)
250 case "unplug":
251 if len(args) < 2 {
252 errx("usage: honk unplug servername")
253 }
254 name := args[1]
255 unplugserver(name)
256 case "backup":
257 if len(args) < 2 {
258 errx("usage: honk backup dirname")
259 }
260 name := args[1]
261 svalbard(name)
262 case "ping":
263 if len(args) < 3 {
264 errx("usage: honk ping (from username) (to username or url)")
265 }
266 name := args[1]
267 targ := args[2]
268 user, err := butwhatabout(name)
269 if err != nil {
270 errx("unknown user %s", name)
271 }
272 ping(user, targ)
273 case "run":
274 serve()
275 case "backend":
276 backendServer()
277 case "test":
278 ElaborateUnitTests()
279 default:
280 errx("unknown command")
281 }
282}