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 user, _ := butwhatabout("test")
55 syndicate(user, "https://mastodon.social/tags/mastoadmin.rss")
56}
57
58func unplugserver(hostname string) {
59 db := opendatabase()
60 xid := fmt.Sprintf("https://%s", hostname)
61 db.Exec("delete from honkers where xid = ? and flavor = 'dub'", xid)
62 db.Exec("delete from doovers where rcpt = ?", xid)
63 xid += "/%"
64 db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
65 db.Exec("delete from doovers where rcpt like ?", xid)
66}
67
68func reexecArgs(cmd string) []string {
69 args := []string{"-datadir", dataDir}
70 args = append(args, log.Args()...)
71 args = append(args, cmd)
72 return args
73}
74
75var elog, ilog, dlog *golog.Logger
76
77func errx(msg string, args ...interface{}) {
78 fmt.Fprintf(os.Stderr, msg+"\n", args...)
79 os.Exit(1)
80}
81
82var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
83var memprofile = flag.String("memprofile", "", "write memory profile to this file")
84var memprofilefd *os.File
85
86func main() {
87 flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
88 flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
89 flag.Parse()
90 if *cpuprofile != "" {
91 f, err := os.Create(*cpuprofile)
92 if err != nil {
93 errx("can't open cpu profile: %s", err)
94 }
95 pprof.StartCPUProfile(f)
96 }
97 if *memprofile != "" {
98 f, err := os.Create(*memprofile)
99 if err != nil {
100 errx("can't open mem profile: %s", err)
101 }
102 memprofilefd = f
103 }
104
105 log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
106 elog = log.E
107 ilog = log.I
108 dlog = log.D
109
110 if os.Geteuid() == 0 {
111 elog.Fatalf("do not run honk as root")
112 }
113
114 args := flag.Args()
115 cmd := "run"
116 if len(args) > 0 {
117 cmd = args[0]
118 }
119 switch cmd {
120 case "init":
121 initdb()
122 case "upgrade":
123 upgradedb()
124 case "version":
125 fmt.Println(softwareVersion)
126 os.Exit(0)
127 }
128 db := opendatabase()
129 dbversion := 0
130 getconfig("dbversion", &dbversion)
131 if dbversion != myVersion {
132 elog.Fatal("incorrect database version. run upgrade.")
133 }
134 getconfig("servermsg", &serverMsg)
135 getconfig("aboutmsg", &aboutMsg)
136 getconfig("loginmsg", &loginMsg)
137 getconfig("servername", &serverName)
138 getconfig("masqname", &masqName)
139 if masqName == "" {
140 masqName = serverName
141 }
142 serverPrefix = serverURL("/")
143 getconfig("usersep", &userSep)
144 getconfig("honksep", &honkSep)
145 getconfig("devel", &develMode)
146 if develMode {
147 gogglesDoNothing()
148 }
149 getconfig("fasttimeout", &fastTimeout)
150 getconfig("slowtimeout", &slowTimeout)
151 getconfig("honkwindow", &honkwindow)
152 honkwindow *= 24 * time.Hour
153
154 prepareStatements(db)
155
156 dbx := opendatabasex()
157 if dbx == nil {
158 elog.Fatal("dbx is nil")
159 }
160 prepareStatementsx(dbx)
161
162 switch cmd {
163 case "admin":
164 adminscreen()
165 case "import":
166 if len(args) != 4 {
167 errx("import username honk|mastodon|twitter srcdir")
168 }
169 importMain(args[1], args[2], args[3])
170 case "export":
171 if len(args) != 3 {
172 errx("export username destdir")
173 }
174 export(args[1], args[2])
175 case "devel":
176 if len(args) != 2 {
177 errx("need an argument: devel (on|off)")
178 }
179 switch args[1] {
180 case "on":
181 setconfig("devel", 1)
182 case "off":
183 setconfig("devel", 0)
184 default:
185 errx("argument must be on or off")
186 }
187 case "setconfig":
188 if len(args) != 3 {
189 errx("need an argument: setconfig key val")
190 }
191 var val interface{}
192 var err error
193 if val, err = strconv.Atoi(args[2]); err != nil {
194 val = args[2]
195 }
196 setconfig(args[1], val)
197 case "adduser":
198 adduser()
199 case "deluser":
200 if len(args) < 2 {
201 errx("usage: honk deluser username")
202 }
203 deluser(args[1])
204 case "chpass":
205 if len(args) < 2 {
206 errx("usage: honk chpass username")
207 }
208 chpass(args[1])
209 case "follow":
210 if len(args) < 3 {
211 errx("usage: honk follow username url")
212 }
213 user, err := butwhatabout(args[1])
214 if err != nil {
215 errx("user %s not found", args[1])
216 }
217 var meta HonkerMeta
218 mj, _ := jsonify(&meta)
219 honkerid, flavor, err := savehonker(user, args[2], "", "presub", "", mj)
220 if err != nil {
221 errx("had some trouble with that: %s", err)
222 }
223 if flavor == "presub" {
224 followyou(user, honkerid, true)
225 }
226 case "unfollow":
227 if len(args) < 3 {
228 errx("usage: honk unfollow username url")
229 }
230 user, err := butwhatabout(args[1])
231 if err != nil {
232 errx("user not found")
233 }
234 row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
235 var honkerid int64
236 err = row.Scan(&honkerid)
237 if err != nil {
238 errx("sorry couldn't find them")
239 }
240 unfollowyou(user, honkerid, true)
241 case "sendmsg":
242 if len(args) < 4 {
243 errx("usage: honk send username filename rcpt")
244 }
245 user, err := butwhatabout(args[1])
246 if err != nil {
247 errx("user %s not found", args[1])
248 }
249 data, err := os.ReadFile(args[2])
250 if err != nil {
251 errx("can't read file: %s", err)
252 }
253 deliverate(user.ID, args[3], data)
254 case "cleanup":
255 arg := "30"
256 if len(args) > 1 {
257 arg = args[1]
258 }
259 cleanupdb(arg)
260 case "unplug":
261 if len(args) < 2 {
262 errx("usage: honk unplug servername")
263 }
264 name := args[1]
265 unplugserver(name)
266 case "backup":
267 if len(args) < 2 {
268 errx("usage: honk backup dirname")
269 }
270 name := args[1]
271 svalbard(name)
272 case "ping":
273 if len(args) < 3 {
274 errx("usage: honk ping (from username) (to username or url)")
275 }
276 name := args[1]
277 targ := args[2]
278 user, err := butwhatabout(name)
279 if err != nil {
280 errx("unknown user %s", name)
281 }
282 ping(user, targ)
283 case "run":
284 serve()
285 case "backend":
286 backendServer()
287 case "test":
288 ElaborateUnitTests()
289 default:
290 errx("unknown command")
291 }
292}