a b/src/main.cxx
1
/* Copyright (C) 2015 J.F.Dockes
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
/////////////////////////////////////////////////////////////////////
18
// Main program
19
20
#include <errno.h>             
21
#include <fcntl.h>             
22
#include <pwd.h>               
23
#include <signal.h>            
24
#include <stdio.h>             
25
#include <stdlib.h>            
26
#include <sys/param.h>         
27
#include <unistd.h>            
28
#include <grp.h>
29
30
#include <iostream>            
31
#include <string>              
32
#include <unordered_map>       
33
#include <vector>              
34
35
#include "libupnpp/log.hxx"    
36
#include "libupnpp/upnpplib.hxx"
37
#include "execmd.h"
38
#include "conftree.hxx"
39
#include "mpdcli.hxx"
40
#include "upmpd.hxx"
41
#include "httpfs.hxx"
42
43
using namespace std;
44
using namespace UPnPP;
45
46
static char *thisprog;
47
48
static int op_flags;
49
#define OPT_MOINS 0x1
50
#define OPT_h     0x2
51
#define OPT_p     0x4
52
#define OPT_d     0x8
53
#define OPT_D     0x10
54
#define OPT_c     0x20
55
#define OPT_l     0x40
56
#define OPT_f     0x80
57
#define OPT_q     0x100
58
#define OPT_i     0x200
59
#define OPT_P     0x400
60
#define OPT_O     0x800
61
#define OPT_v     0x1000
62
63
static const char usage[] = 
64
    "-c configfile \t configuration file to use\n"
65
    "-h host    \t specify host MPD is running on\n"
66
    "-p port     \t specify MPD port\n"
67
    "-d logfilename\t debug messages to\n"
68
    "-l loglevel\t  log level (0-6)\n"
69
    "-D    \t run as a daemon\n"
70
    "-f friendlyname\t define device displayed name\n"
71
    "-q 0|1\t if set, we own the mpd queue, else avoid clearing it whenever we feel like it\n"
72
    "-i iface    \t specify network interface name to be used for UPnP\n"
73
    "-P upport    \t specify port number to be used for UPnP\n"
74
    "-O 0|1\t decide if we run and export the OpenHome services\n"
75
    "-v      \tprint version info\n"
76
    "\n"
77
    ;
78
79
static void
80
versionInfo(FILE *fp)
81
{
82
    fprintf(fp, "Upmpdcli %s %s\n",
83
           UPMPDCLI_PACKAGE_VERSION, LibUPnP::versionString().c_str());
84
}
85
86
static void
87
Usage(FILE *fp = stderr)
88
{
89
    fprintf(fp, "%s: usage:\n%s", thisprog, usage);
90
    versionInfo(fp);
91
    exit(1);
92
}
93
94
95
static const string dfltFriendlyName("UpMpd");
96
97
// This is global
98
string g_protocolInfo;
99
100
// Static for cleanup in sig handler.
101
static UpnpDevice *dev;
102
103
string g_datadir(DATADIR "/");
104
105
// Global
106
string g_configfilename(CONFIGDIR "/upmpdcli.conf");
107
ConfSimple *g_config;
108
109
static void onsig(int)
110
{
111
    LOGDEB("Got sig" << endl);
112
    dev->shouldExit();
113
}
114
115
static const int catchedSigs[] = {SIGINT, SIGQUIT, SIGTERM};
116
static void setupsigs()
117
{
118
    struct sigaction action;
119
    action.sa_handler = onsig;
120
    action.sa_flags = 0;
121
    sigemptyset(&action.sa_mask);
122
    for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++)
123
        if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) {
124
            if (sigaction(catchedSigs[i], &action, 0) < 0) {
125
                perror("Sigaction failed");
126
            }
127
        }
128
}
129
130
int main(int argc, char *argv[])
131
{
132
    // Path for the sc2mpd command, or empty
133
    string sc2mpdpath;
134
135
    // Sender mode: path for the command creating the mpd and mpd2sc
136
    // processes, and port for the auxiliary mpd.
137
    string senderpath;
138
    int sendermpdport = 6700;
139
140
    // Main MPD parameters
141
    string mpdhost("localhost");
142
    int mpdport = 6600;
143
    string mpdpassword;
144
145
    string logfilename;
146
    int loglevel(Logger::LLINF);
147
    string friendlyname(dfltFriendlyName);
148
    bool ownqueue = true;
149
    bool enableAV = true;
150
    bool enableOH = true;
151
    bool ohmetapersist = true;
152
    string upmpdcliuser("upmpdcli");
153
    string pidfilename("/var/run/upmpdcli.pid");
154
    string iconpath(DATADIR "/icon.png");
155
    string presentationhtml(DATADIR "/presentation.html");
156
    string iface;
157
    unsigned short upport = 0;
158
    string upnpip;
159
160
    const char *cp;
161
    if ((cp = getenv("UPMPD_HOST")))
162
        mpdhost = cp;
163
    if ((cp = getenv("UPMPD_PORT")))
164
        mpdport = atoi(cp);
165
    if ((cp = getenv("UPMPD_FRIENDLYNAME")))
166
        friendlyname = atoi(cp);
167
    if ((cp = getenv("UPMPD_CONFIG")))
168
        g_configfilename = cp;
169
    if ((cp = getenv("UPMPD_UPNPIFACE")))
170
        iface = cp;
171
    if ((cp = getenv("UPMPD_UPNPPORT")))
172
        upport = atoi(cp);
173
174
    thisprog = argv[0];
175
    argc--; argv++;
176
    while (argc > 0 && **argv == '-') {
177
        (*argv)++;
178
        if (!(**argv))
179
            Usage();
180
        while (**argv)
181
            switch (*(*argv)++) {
182
            case 'c':   op_flags |= OPT_c; if (argc < 2)  Usage();
183
                g_configfilename = *(++argv); argc--; goto b1;
184
            case 'D':   op_flags |= OPT_D; break;
185
            case 'd':   op_flags |= OPT_d; if (argc < 2)  Usage();
186
                logfilename = *(++argv); argc--; goto b1;
187
            case 'f':   op_flags |= OPT_f; if (argc < 2)  Usage();
188
                friendlyname = *(++argv); argc--; goto b1;
189
            case 'h':   op_flags |= OPT_h; if (argc < 2)  Usage();
190
                mpdhost = *(++argv); argc--; goto b1;
191
            case 'i':   op_flags |= OPT_i; if (argc < 2)  Usage();
192
                iface = *(++argv); argc--; goto b1;
193
            case 'l':   op_flags |= OPT_l; if (argc < 2)  Usage();
194
                loglevel = atoi(*(++argv)); argc--; goto b1;
195
            case 'O': {
196
                op_flags |= OPT_O; 
197
                if (argc < 2)  Usage();
198
                const char *cp =  *(++argv);
199
                if (*cp == '1' || *cp == 't' || *cp == 'T' || *cp == 'y' || 
200
                    *cp == 'Y')
201
                    enableOH = true;
202
                argc--; goto b1;
203
            }
204
            case 'P':   op_flags |= OPT_P; if (argc < 2)  Usage();
205
                upport = atoi(*(++argv)); argc--; goto b1;
206
            case 'p':   op_flags |= OPT_p; if (argc < 2)  Usage();
207
                mpdport = atoi(*(++argv)); argc--; goto b1;
208
            case 'q':   op_flags |= OPT_q; if (argc < 2)  Usage();
209
                ownqueue = atoi(*(++argv)) != 0; argc--; goto b1;
210
            case 'v': versionInfo(stdout); exit(0); break;
211
            default: Usage();   break;
212
            }
213
    b1: argc--; argv++;
214
    }
215
216
    if (argc != 0)
217
        Usage();
218
219
    UpMpd::Options opts;
220
221
    string cachedir;
222
    string onstart;
223
    string onstop;
224
    string onvolumechange;
225
    if (!g_configfilename.empty()) {
226
        g_config = new ConfSimple(g_configfilename.c_str(), 1, true);
227
        if (!g_config || !g_config->ok()) {
228
            cerr << "Could not open config: " << g_configfilename << endl;
229
            return 1;
230
        }
231
232
        string value;
233
        if (!(op_flags & OPT_d))
234
            g_config->get("logfilename", logfilename);
235
        if (!(op_flags & OPT_f))
236
            g_config->get("friendlyname", friendlyname);
237
        if (!(op_flags & OPT_l) && g_config->get("loglevel", value))
238
            loglevel = atoi(value.c_str());
239
        if (!(op_flags & OPT_h))
240
            g_config->get("mpdhost", mpdhost);
241
        if (!(op_flags & OPT_p) && g_config->get("mpdport", value)) {
242
            mpdport = atoi(value.c_str());
243
        }
244
        g_config->get("mpdpassword", mpdpassword);
245
        if (!(op_flags & OPT_q) && g_config->get("ownqueue", value)) {
246
            ownqueue = atoi(value.c_str()) != 0;
247
        }
248
        if (g_config->get("openhome", value)) {
249
            enableOH = atoi(value.c_str()) != 0;
250
        }
251
        if (g_config->get("upnpav", value)) {
252
            enableAV = atoi(value.c_str()) != 0;
253
        }
254
        if (g_config->get("ohmetapersist", value)) {
255
            ohmetapersist = atoi(value.c_str()) != 0;
256
        }
257
        g_config->get("iconpath", iconpath);
258
        g_config->get("presentationhtml", presentationhtml);
259
        g_config->get("cachedir", cachedir);
260
        g_config->get("onstart", onstart);
261
        g_config->get("onstop", onstop);
262
        g_config->get("onvolumechange", onvolumechange);
263
        if (!(op_flags & OPT_i)) {
264
            g_config->get("upnpiface", iface);
265
            if (iface.empty()) {
266
                g_config->get("upnpip", upnpip);
267
            }
268
        }
269
        if (!(op_flags & OPT_P) && g_config->get("upnpport", value)) {
270
            upport = atoi(value.c_str());
271
        }
272
        if (g_config->get("schttpport", value))
273
            opts.schttpport = atoi(value.c_str());
274
        g_config->get("scplaymethod", opts.scplaymethod);
275
        g_config->get("sc2mpd", sc2mpdpath);
276
        if (g_config->get("ohmetasleep", value))
277
            opts.ohmetasleep = atoi(value.c_str());
278
279
        g_config->get("scsenderpath", senderpath);
280
        if (g_config->get("scsendermpdport", value))
281
            sendermpdport = atoi(value.c_str());
282
    }
283
    if (Logger::getTheLog(logfilename) == 0) {
284
        cerr << "Can't initialize log" << endl;
285
        return 1;
286
    }
287
    Logger::getTheLog("")->setLogLevel(Logger::LogLevel(loglevel));
288
289
    Pidfile pidfile(pidfilename);
290
291
    // If started by root, do the pidfile + change uid thing
292
    uid_t runas(0);
293
    gid_t runasg(0);
294
    if (geteuid() == 0) {
295
        struct passwd *pass = getpwnam(upmpdcliuser.c_str());
296
        if (pass == 0) {
297
            LOGFAT("upmpdcli won't run as root and user " << upmpdcliuser << 
298
                   " does not exist " << endl);
299
            return 1;
300
        }
301
        runas = pass->pw_uid;
302
        runasg = pass->pw_gid;
303
304
        pid_t pid;
305
        if ((pid = pidfile.open()) != 0) {
306
            LOGFAT("Can't open pidfile: " << pidfile.getreason() << 
307
                   ". Return (other pid?): " << pid << endl);
308
            return 1;
309
        }
310
        if (pidfile.write_pid() != 0) {
311
            LOGFAT("Can't write pidfile: " << pidfile.getreason() << endl);
312
            return 1;
313
        }
314
  if (cachedir.empty())
315
            cachedir = "/var/cache/upmpdcli";
316
    } else {
317
  if (cachedir.empty())
318
            cachedir = path_cat(path_tildexpand("~") , "/.cache/upmpdcli");
319
    }
320
321
    string& mcfn = opts.cachefn;
322
    if (ohmetapersist) {
323
        opts.cachefn = path_cat(cachedir, "/metacache");
324
        if (!path_makepath(cachedir, 0755)) {
325
            LOGERR("makepath("<< cachedir << ") : errno : " << errno << endl);
326
        } else {
327
            int fd;
328
            if ((fd = open(mcfn.c_str(), O_CREAT|O_RDWR, 0644)) < 0) {
329
                LOGERR("creat("<< mcfn << ") : errno : " << errno << endl);
330
            } else {
331
                close(fd);
332
                if (geteuid() == 0 && chown(mcfn.c_str(), runas, -1) != 0) {
333
                    LOGERR("chown("<< mcfn << ") : errno : " << errno << endl);
334
                }
335
                if (geteuid() == 0 && chown(cachedir.c_str(), runas, -1) != 0) {
336
                    LOGERR("chown("<< cachedir << ") : errno : " << errno << endl);
337
                }
338
            }
339
        }
340
    }
341
    
342
    if ((op_flags & OPT_D)) {
343
        if (daemon(1, 0)) {
344
            LOGFAT("Daemon failed: errno " << errno << endl);
345
            return 1;
346
        }
347
    }
348
349
    if (geteuid() == 0) {
350
        // Need to rewrite pid, it may have changed with the daemon call
351
        pidfile.write_pid();
352
        if (!logfilename.empty() && logfilename.compare("stderr")) {
353
            if (chown(logfilename.c_str(), runas, -1) < 0) {
354
                LOGERR("chown("<<logfilename<<") : errno : " << errno << endl);
355
            }
356
        }
357
        if (initgroups(upmpdcliuser.c_str(), runasg) < 0) {
358
            LOGERR("initgroup failed. Errno: " << errno << endl);
359
        }
360
        if (setuid(runas) < 0) {
361
            LOGFAT("Can't set my uid to " << runas << " current: " << geteuid()
362
                   << endl);
363
            return 1;
364
        }
365
#if 0        
366
        gid_t list[100];
367
        int ng = getgroups(100, list);
368
        cerr << "GROUPS: ";
369
        for (int i = 0; i < ng; i++) {
370
            cerr << int(list[i]) << " ";
371
        }
372
        cerr << endl;
373
#endif
374
    }
375
376
//// Dropped root 
377
378
    if (sc2mpdpath.empty()) {
379
        // Do we have an sc2mpd command installed (for songcast)?
380
        if (!ExecCmd::which("sc2mpd", sc2mpdpath))
381
            sc2mpdpath.clear();
382
    }
383
    if (senderpath.empty()) {
384
        // Do we have an scmakempdsender command installed (for
385
        // starting the songcast sender and its auxiliary mpd)?
386
        if (!ExecCmd::which("scmakempdsender", senderpath))
387
            senderpath.clear();
388
    }
389
    
390
    if (!sc2mpdpath.empty()) {
391
        // Check if sc2mpd is actually there
392
        if (access(sc2mpdpath.c_str(), X_OK|R_OK) != 0) {
393
            LOGERR("Specified path for sc2mpd: " << sc2mpdpath << 
394
                   " is not executable" << endl);
395
            sc2mpdpath.clear();
396
        }
397
    }
398
399
    if (!senderpath.empty()) {
400
        // Check that both the starter script and the mpd2sc sender
401
        // command are executable. We'll assume that mpd is ok
402
        if (access(senderpath.c_str(), X_OK|R_OK) != 0) {
403
            LOGERR("The specified path for the sender starter script: ["
404
                   << senderpath <<
405
                   "] is not executable, disabling the sender mode.\n");
406
            senderpath.clear();
407
        } else {
408
            string path;
409
            if (!ExecCmd::which("mpd2sc", path)) {
410
                LOGERR("Sender starter was specified and found but the mpd2sc "
411
                       "command is not found (or executable). Disabling "
412
                       "the sender mode.\n");
413
                senderpath.clear();
414
            }
415
        }
416
    }
417
418
419
    // Initialize MPD client object. Retry until it works or power fail.
420
    MPDCli *mpdclip = 0;
421
    int mpdretrysecs = 2;
422
    for (;;) {
423
        mpdclip = new MPDCli(mpdhost, mpdport, mpdpassword, onstart, onstop,
424
                             onvolumechange);
425
        if (mpdclip == 0) {
426
            LOGFAT("Can't allocate MPD client object" << endl);
427
            return 1;
428
        }
429
        if (!mpdclip->ok()) {
430
            LOGERR("MPD connection failed" << endl);
431
            delete mpdclip;
432
            mpdclip = 0;
433
            sleep(mpdretrysecs);
434
            mpdretrysecs = MIN(2*mpdretrysecs, 120);
435
        } else {
436
            break;
437
        }
438
    }
439
440
    // Initialize libupnpp, and check health
441
    LibUPnP *mylib = 0;
442
    string hwaddr;
443
    int libretrysecs = 10;
444
    for (;;) {
445
        // Libupnp init fails if we're started at boot and the network
446
        // is not ready yet. So retry this forever
447
        mylib = LibUPnP::getLibUPnP(true, &hwaddr, iface, upnpip, upport);
448
        if (mylib) {
449
            break;
450
        }
451
        sleep(libretrysecs);
452
        libretrysecs = MIN(2*libretrysecs, 120);
453
    }
454
455
    if (!mylib->ok()) {
456
        LOGFAT("Lib init failed: " <<
457
               mylib->errAsString("main", mylib->getInitError()) << endl);
458
        return 1;
459
    }
460
461
    if ((cp = getenv("UPMPDCLI_UPNPLOGFILENAME"))) {
462
        char *cp1 = getenv("UPMPDCLI_UPNPLOGLEVEL");
463
        int loglevel = LibUPnP::LogLevelNone;
464
        if (cp1) {
465
            loglevel = atoi(cp1);
466
        }
467
        loglevel = loglevel < 0 ? 0: loglevel;
468
        loglevel = loglevel > int(LibUPnP::LogLevelDebug) ? 
469
            int(LibUPnP::LogLevelDebug) : loglevel;
470
471
        if (loglevel != LibUPnP::LogLevelNone) {
472
            mylib->setLogFileName(cp, LibUPnP::LogLevel(loglevel));
473
        }
474
    }
475
476
    // Create unique ID
477
    string UUID = LibUPnP::makeDevUUID(friendlyname, hwaddr);
478
479
    // Initialize the data we serve through HTTP (device and service
480
    // descriptions, icons, presentation page, etc.)
481
    unordered_map<string, VDirContent> files;
482
    if (!initHttpFs(files, g_datadir, UUID, friendlyname, enableAV, enableOH,
483
                    !senderpath.empty(),
484
                    iconpath, presentationhtml)) {
485
        exit(1);
486
    }
487
488
    if (ownqueue)
489
        opts.options |= UpMpd::upmpdOwnQueue;
490
    if (enableOH)
491
        opts.options |= UpMpd::upmpdDoOH;
492
    if (ohmetapersist)
493
        opts.options |= UpMpd::upmpdOhMetaPersist;
494
    if (!sc2mpdpath.empty()) {
495
        opts.sc2mpdpath = sc2mpdpath;
496
        opts.options |= UpMpd::upmpdOhReceiver;
497
    }
498
    if (!senderpath.empty()) {
499
        opts.options |= UpMpd::upmpdOhSenderReceiver;
500
        opts.senderpath = senderpath;
501
        opts.sendermpdport = sendermpdport;
502
    }
503
504
    if (!enableAV)
505
        opts.options |= UpMpd::upmpdNoAV;
506
    // Initialize the UPnP device object.
507
    UpMpd device(string("uuid:") + UUID, friendlyname, 
508
                 files, mpdclip, opts);
509
    dev = &device;
510
511
    // And forever generate state change events.
512
    LOGDEB("Entering event loop" << endl);
513
    setupsigs();
514
    device.eventloop();
515
    LOGDEB("Event loop returned" << endl);
516
517
    return 0;
518
}