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