This file is indexed.

/usr/bin/ds-logpipe is in 389-ds-base 1.3.7.10-1ubuntu1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
#! /usr/bin/python2

from __future__ import print_function

import sys
import os, os.path
import errno
import signal
import pprint
import types
import time
import fcntl
import pwd

maxlines = 1000 # set on command line
S_IFIFO = 0o010000

buffer = [] # default circular buffer used by default plugin
totallines = 0
logfname = "" # name of log pipe
debug = False

# default plugin just keeps a circular buffer
def defaultplugin(line):
    global totallines
    buffer.append(line)
    totallines = totallines + 1
    if len(buffer) > maxlines:
        del buffer[0]
    return True

def printbuffer():
    sys.stdout.writelines(buffer)
    print("Read %d total lines" % totallines)
    print(logfname, "=" * 60)
    sys.stdout.flush()

def defaultpost(): printbuffer()

plgfuncs = [] # list of plugin functions
plgpostfuncs = [] # list of post plugin funcs

def finish():
    for postfunc in plgpostfuncs: postfunc()
    if options.scriptpidfile: os.unlink(options.scriptpidfile)
    sys.exit(0)

def sighandler(signum, frame):
    if signum != signal.SIGHUP:
        signal.signal(signal.SIGHUP, signal.SIG_DFL)
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        #signal.signal(signal.SIGPIPE, signal.SIG_DFL)
        signal.signal(signal.SIGTERM, signal.SIG_DFL)
        signal.signal(signal.SIGALRM, signal.SIG_DFL)
        if signum == signal.SIGALRM and debug:
            print("script timed out waiting to open pipe")
        finish()
    else: printbuffer()

def isvalidpluginfile(plg):
    return os.path.isfile(plg)

def my_import(plgfile):
    '''import plgfile as a python module and return
    an error string if error - also return the prefunc if any'''
    if not isvalidpluginfile(plgfile):
        return ("%s is not a valid plugin filename" % plgfile, None, None)
    # __import__ searches for the file in sys.path - we cannot
    # __import__ a file by the full path
    # __import__('basename') looks for basename.py in sys.path
    (dir, fname) = os.path.split(plgfile)
    base = os.path.splitext(fname)[0]
    if not dir: dir = "."
    sys.path.insert(0, dir) # put our path first so it will find our file
    mod = __import__(base) # will throw exception if problem with python file
    sys.path.pop(0) # remove our path

    # check for the plugin functions
    plgfunc = getattr(mod, 'plugin', None)
    if not plgfunc:
        return ('%s does not specify a plugin function' % plgfile, None, base)
    if not isinstance(plgfunc, types.FunctionType):
        return ('the symbol "plugin" in %s is not a function' % plgfile, None, base)
    plgfuncs.append(plgfunc) # add to list in cmd line order

    # check for 'post' func
    plgpostfunc = getattr(mod, 'post', None)
    if plgpostfunc:
        if not isinstance(plgpostfunc, types.FunctionType):
            return ('the symbol "post" in %s is not a function' % plgfile, None, base)
        else:
            plgpostfuncs.append(plgpostfunc) # add to list in cmd line order

    prefunc = getattr(mod, 'pre', None)
    # check for 'pre' func
    if prefunc and not isinstance(prefunc, types.FunctionType):
        return ('the symbol "pre" in %s is not a function' % plgfile, None, base)

    return ('', prefunc, base)

def parse_plugins(parser, options, args):
    '''Each plugin in the plugins list may have additional
    arguments, specified on the command line like this:
    --plugin=foo.py foo.bar=1 foo.baz=2 ...
    that is, each argument to plugin X will be specified as X.arg=value'''
    if not options.plugins: return args

    for plgfile in options.plugins:
        (errstr, prefunc, base) = my_import(plgfile)
        if errstr:
            parser.error(errstr)
            return args

        # parse the arguments to the plugin given on the command line
        bvals = {} # holds plugin args and values, if any
        newargs = []
        for arg in args:
            if arg.startswith(base + '.'):
                argval = arg.replace(base + '.', '')
                (plgarg, plgval) = argval.split('=', 1) # split at first =
                if not plgarg in bvals:
                    bvals[plgarg] = plgval
                elif isinstance(bvals[plgarg],list):
                    bvals[plgarg].append(plgval)
                else: # convert to list
                    bvals[plgarg] = [bvals[plgarg], plgval]
            else:
                newargs.append(arg)
        if prefunc:
            if debug:
                print('Calling "pre" function in', plgfile)
            if not prefunc(bvals):
                parser.error('the "pre" function in %s returned an error' % plgfile)
        args = newargs

    return args

def open_pipe(logfname):
    opencompleted = False
    logf = None
    while not opencompleted:
        try:
            logf = open(logfname, 'r') # blocks until there is some input
            opencompleted = True
        except IOError as e:
            if e.errno == errno.EINTR:
                continue # open was interrupted, try again
            else: # hard error
                print("%s [%d]" % (e.strerror, e.errno))
                sys.exit(1)
    return logf

def is_proc_alive(procpid):
    retval = False
    try:
        retval = os.path.exists("/proc/%d" % procpid)
    except IOError as e:
        if e.errno != errno.ENOENT: # may not exist yet - that's ok
            # otherwise, probably permissions or other badness
            print("could not open file %s - %s [%d]" % (procfile, e.strerror, e.errno))
            sys.exit(1)
    # using /proc/pid failed, try kill
    if not retval:
        try:
            os.kill(procpid, 0) # sig 0 is a "ping"
            retval = True # if we got here, proc exists
        except OSError as e:
            pass # no such process, or EPERM/EACCES
    return retval

def get_pid_from_file(pidfile):
    procpid = 0
    if pidfile:
        line = None
        try:
            pfd = open(pidfile, 'r')
            line = pfd.readline()
            pfd.close()
        except IOError as e:
            if e.errno != errno.ENOENT: # may not exist yet - that's ok
                # otherwise, probably permissions or other badness
                print("Could not read pid from file %s - %s [%d]" % (pidfile, e.strerror, e.errno))
                sys.exit(1)
        if line:
            procpid = int(line)
    return procpid

def write_pid_file(pidfile):
    try:
        pfd = open(pidfile, 'w')
        pfd.write("%d\n" % os.getpid())
        pfd.close()
    except IOError as e:
        print("Could not write pid to file %s - %s [%d]" % (pidfile, e.strerror, e.errno))
        sys.exit(1)

def handle_script_pidfile(scriptpidfile):
    scriptpid = get_pid_from_file(scriptpidfile)
    # 0 if no file or no pid or error
    if scriptpid and is_proc_alive(scriptpid):
        # already running
        if debug:
            print("Script is already running: process id %d" % scriptpid)
        return False
    else:
        # either process is not running or no file
        # write our pid to the file
        write_pid_file(scriptpidfile)
    return True

def read_and_process_line(logf, plgfuncs):
    line = None
    done = False
    readcompleted = False
    while not readcompleted:
        try:
            line = logf.readline()
            readcompleted = True # read completed
        except IOError as e:
            if e.errno == errno.EINTR:
                continue # read was interrupted, try again
            else: # hard error
                print("%s [%d]" % (e.strerror, e.errno))
                sys.exit(1)
    if line: # read something
        for plgfunc in plgfuncs:
            if not plgfunc(line):
                print("Aborting processing due to function %s.%s" % (plgfunc.__module__, plgfunc.__name__))
                finish() # this will exit the process
                done = True
                break
    else: # EOF
        done = True
    return done

def parse_options():
    from optparse import OptionParser
    usage = "%prog <name of pipe> [options]"
    parser = OptionParser(usage)
    parser.add_option("-m", "--maxlines", dest="maxlines", type='int',
                      help="maximum number of lines to keep in the buffer", default=1000)
    parser.add_option("-d", "--debug", dest="debug", action="store_true",
                      default=False, help="gather extra debugging information")
    parser.add_option("-p", "--plugin", type='string', dest='plugins', action='append',
                      help='filename of a plugin to use with this log')
    parser.add_option("-s", "--serverpidfile", type='string', dest='serverpidfile',
                      help='name of file containing the pid of the server to monitor')
    parser.add_option("-t", "--servertimeout", dest="servertimeout", type='int',
                      help="timeout in seconds to wait for the serverpid to be alive. only applies when using -s or --serverpid", default=60)
    parser.add_option("--serverpid", dest="serverpid", type='int',
                      help="process id of server to monitor", default=0)
    parser.add_option("-u", "--user", type='string', dest='user',
                      help='name of user to set effective uid to')
    parser.add_option("-i", "--scriptpidfile", type='string', dest='scriptpidfile',
                      help='name of file containing the pid of this script')

    options, args = parser.parse_args()

    args = parse_plugins(parser, options, args)

    if len(args) < 1:
        parser.error("You must specify the name of the pipe to use")
    if len(args) > 1:
        parser.error("error - unhandled command line arguments: %s" % ' '.join(args))

    return options, args[0]

options, logfname = parse_options()

if options.debug:
    debug = True

if len(plgfuncs) == 0:
    plgfuncs.append(defaultplugin)
if len(plgpostfuncs) == 0:
    plgpostfuncs.append(defaultpost)

if options.user:
    try:
        userid = int(options.user)
    except ValueError:  # not a numeric userid - look it up
        try:
            userid = pwd.getpwnam(options.user)[2]
        except Exception as e:
            print("Failed to lookup name (%s) error: %s" %
                  (options.user, str(e)))
            sys.exit(1)
    os.seteuid(userid)

if options.scriptpidfile:
    if not handle_script_pidfile(options.scriptpidfile):
        options.scriptpidfile = None
        sys.exit(1)

serverpid = options.serverpid
if serverpid:
    if not is_proc_alive(serverpid):
        print("Server pid [%d] is not alive - exiting" % serverpid)
        sys.exit(1)

try:
    if os.stat(logfname).st_mode & S_IFIFO:
        if debug:
            print("Using existing log pipe", logfname)
    else:
        print("Error:", logfname, "exists and is not a log pipe")
        print("use a filename other than", logfname)
        sys.exit(1)
except OSError as e:
    if e.errno == errno.ENOENT:
        if debug:
            print("Creating log pipe", logfname)
        try:
            os.mkfifo(logfname)
            os.chmod(logfname, 0o600)
        except Exception as e:
            print("Failed to create log pipe: " + str(e))
            sys.exit(1)
    else:
        print("Failed to create log pipe - %s [error %d]" % (e.strerror, e.errno))
        sys.exit(1)

if debug:
    print("Listening to log pipe", logfname, "number of lines", maxlines)

# set up our signal handlers
signal.signal(signal.SIGHUP, sighandler)
signal.signal(signal.SIGINT, sighandler)
#signal.signal(signal.SIGPIPE, sighandler)
signal.signal(signal.SIGTERM, sighandler)
signal.signal(signal.SIGALRM, sighandler)

timerisset = False
neverdone = False
if options.serverpidfile:
    # start the timer to wait for the pid file to be available
    signal.setitimer(signal.ITIMER_REAL, options.servertimeout)
    timerisset = True

# if we are tracking a server, we will be done
# when the server exits
# if not tracking a server, we will only be done
# when we are killed
if not serverpid and not options.serverpidfile:
    neverdone = True

done = False
while not done:
    # open the pipe - will hang until
    # 1. something opens the other end
    # 2. alarm goes off - will just exit
    logf = open_pipe(logfname)
    # if we get here, logf is not None
    if debug:
        print("opened pipe", logf)

    if timerisset:
        # cancel the timer - the open succeeded
        timerisset = False
        signal.setitimer(signal.ITIMER_REAL, 0)
        if debug:
            print("cancelled startup timer")

    lines = 0
    # read and process the next line in the pipe
    # if server exits while we are reading, we will get
    # EOF and the func will return True - will also
    # return True if a plugin returns failure
    while not read_and_process_line(logf, plgfuncs):
        lines += 1

    # the other end of the pipe closed - we close our end too
    if debug:
        print("read", lines, "lines")
    logf.close()
    logf = None
    if debug:
        print("closed log pipe", logfname)

    if not serverpid and options.serverpidfile:
        # see if the server has written its server pid file yet
        # it may take a "long time" for the server to actually
        # write its pid file
        serverpid = get_pid_from_file(options.serverpidfile)

    # if the server is no longer running, just finish
    if serverpid and not is_proc_alive(serverpid):
        done = True
        if debug:
            print("server pid", serverpid, "exited - script exiting")

    if neverdone:
        done = False
    elif not done:
        if not lines:
            # at startup the server will close the log and reopen it
            # when it does this lines will be 0 - this means we need
            # immediately attempt to reopen the log pipe and read it
            # however, at shutdown, the server will close the log before
            # the process has exited - so is_proc_alive will return
            # true for a short time - if we then attempt to open the
            # pipe, the open will hang forever - to avoid this situation
            # we set the alarm again to wake up the open - use a short
            # timeout so we don't wait a long time if the server
            # really is exiting
            signal.setitimer(signal.ITIMER_REAL, 0.25)
            timerisset = True
            if debug:
                print("set startup timer - see if server is really shut down")
        else: # we read something
            # pipe closed - usually when server shuts down
            done = True

    if not done and debug:
        print("log pipe", logfname, "closed - reopening - read", totallines, "total lines")

finish()