aboutsummaryrefslogtreecommitdiffstats
path: root/src/lua/lua_tcp.c
blob: fadb7d93b8d6fe5f1130609cbeff8124353a8f27 (plain)
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
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
/*
 * Copyright (c) 2015, Vsevolod Stakhov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *	 * Redistributions of source code must retain the above copyright
 *	   notice, this list of conditions and the following disclaimer.
 *	 * Redistributions in binary form must reproduce the above copyright
 *	   notice, this list of conditions and the following disclaimer in the
 *	   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "lua_common.h"
#include "buffer.h"
#include "dns.h"
#include "utlist.h"

static void lua_tcp_handler (int fd, short what, gpointer ud);
/***
 * @module rspamd_tcp
 * Rspamd TCP module represents generic TCP asynchronous client available from LUA code.
 * This module hides all complexity: DNS resolving, sessions management, zero-copy
 * text transfers and so on under the hood. It can work in partial or complete modes:
 *
 * - partial mode is used when you need to call a continuation routine each time data is available for read
 * - complete mode calls for continuation merely when all data is read from socket (e.g. when a server sends reply and closes a connection)
 * @example
local logger = require "rspamd_logger"
local tcp = require "rspamd_tcp"

rspamd_config.SYM = function(task)

    local function cb(err, data)
        logger.infox('err: %1, data: %2', err, tostring(data))
    end

    tcp.request({
    	task = task,
    	host = "google.com",
    	port = 80,
    	data = {"GET / HTTP/1.0\r\n", "Host: google.com\r\n", "\r\n"},
    	callback = cb})
end
 */

LUA_FUNCTION_DEF (tcp, request);

static const struct luaL_reg tcp_libf[] = {
	LUA_INTERFACE_DEF (tcp, request),
	{"__tostring", rspamd_lua_class_tostring},
	{NULL, NULL}
};

struct lua_tcp_cbdata {
	lua_State *L;
	struct rspamd_async_session *session;
	struct event_base *ev_base;
	struct timeval tv;
	rspamd_inet_addr_t *addr;
	rspamd_mempool_t *pool;
	struct iovec *iov;
	GString *in;
	gchar *stop_pattern;
	struct event ev;
	gint fd;
	gint cbref;
	guint iovlen;
	guint pos;
	guint total;
	gboolean partial;
	guint16 port;
};

static const int default_tcp_timeout = 5000;

static struct rspamd_dns_resolver *
lua_tcp_global_resolver (struct event_base *ev_base)
{
	static struct rspamd_dns_resolver *global_resolver;

	if (global_resolver == NULL) {
		global_resolver = dns_resolver_init (NULL, ev_base, NULL);
	}

	return global_resolver;
}

static void
lua_tcp_fin (gpointer arg)
{
	struct lua_tcp_cbdata *cbd = (struct lua_tcp_cbdata *)arg;

	luaL_unref (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);

	if (cbd->fd != -1) {
		event_del (&cbd->ev);
		close (cbd->fd);
	}

	if (cbd->addr) {
		rspamd_inet_address_destroy (cbd->addr);
	}

	g_slice_free1 (sizeof (struct lua_tcp_cbdata), cbd);
}

static void
lua_tcp_maybe_free (struct lua_tcp_cbdata *cbd)
{
	if (cbd->session) {
		rspamd_session_remove_event (cbd->session, lua_tcp_fin, cbd);
	}
	else {
		lua_tcp_fin (cbd);
	}
}

static void
lua_tcp_push_error (struct lua_tcp_cbdata *cbd, const char *err)
{
	lua_rawgeti (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);
	lua_pushstring (cbd->L, err);

	if (lua_pcall (cbd->L, 1, 0, 0) != 0) {
		msg_info ("callback call failed: %s", lua_tostring (cbd->L, -1));
	}
}

static void
lua_tcp_push_data (struct lua_tcp_cbdata *cbd, const gchar *str, gsize len)
{
	struct rspamd_lua_text *t;

	lua_rawgeti (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);
	/* Error */
	lua_pushnil (cbd->L);
	/* Body */
	t = lua_newuserdata (cbd->L, sizeof (*t));
	rspamd_lua_setclass (cbd->L, "rspamd{text}", -1);
	t->start = str;
	t->len = len;
	t->own = FALSE;

	if (lua_pcall (cbd->L, 2, 0, 0) != 0) {
		msg_info ("callback call failed: %s", lua_tostring (cbd->L, -1));
	}
}

static void
lua_tcp_write_helper (struct lua_tcp_cbdata *cbd)
{
	struct iovec *start;
	guint niov, i;
	gint flags = 0;
	gsize remain;
	gssize r;
	struct iovec *cur_iov;
	struct msghdr msg;

	if (cbd->pos == cbd->total) {
		goto call_finish_handler;
	}

	start = &cbd->iov[0];
	niov = cbd->iovlen;
	remain = cbd->pos;
	/* We know that niov is small enough for that */
	cur_iov = alloca (niov * sizeof (struct iovec));
	memcpy (cur_iov, cbd->iov, niov * sizeof (struct iovec));
	for (i = 0; i < cbd->iovlen && remain > 0; i++) {
		/* Find out the first iov required */
		start = &cur_iov[i];
		if (start->iov_len <= remain) {
			remain -= start->iov_len;
			start = &cur_iov[i + 1];
			niov--;
		}
		else {
			start->iov_base = (void *)((char *)start->iov_base + remain);
			start->iov_len -= remain;
			remain = 0;
		}
	}

	memset (&msg, 0, sizeof (msg));
	msg.msg_iov = start;
	msg.msg_iovlen = MIN (IOV_MAX, niov);
	g_assert (niov > 0);
#ifdef MSG_NOSIGNAL
	flags = MSG_NOSIGNAL;
#endif
	r = sendmsg (cbd->fd, &msg, flags);

	if (r == -1) {
		lua_tcp_push_error (cbd, "IO write error");
		lua_tcp_maybe_free (cbd);
		return;
	}
	else {
		cbd->pos += r;
	}

	if (cbd->pos >= cbd->total) {
		goto call_finish_handler;
	}
	else {
		/* Want to write more */
		event_add (&cbd->ev, &cbd->tv);
	}

	return;

call_finish_handler:

	if (!cbd->partial) {
		cbd->in = g_string_sized_new (BUFSIZ);
		rspamd_mempool_add_destructor (cbd->pool, rspamd_gstring_free_hard,
				cbd->in);
	}

	event_del (&cbd->ev);
	event_set (&cbd->ev, cbd->fd, EV_READ | EV_PERSIST, lua_tcp_handler, cbd);
	event_base_set (cbd->ev_base, &cbd->ev);
	event_add (&cbd->ev, &cbd->tv);
}

static void
lua_tcp_handler (int fd, short what, gpointer ud)
{
	struct lua_tcp_cbdata *cbd = ud;
	gchar inbuf[BUFSIZ];
	gssize r;
	guint slen;

	if (what == EV_READ) {
		g_assert (cbd->partial || cbd->in != NULL);

		r = read (cbd->fd, inbuf, sizeof (inbuf));

		if (r <= 0) {
			/*
			 * We actually can have connection reset here, so we just check if
			 * the cumulative buffer is not empty
			 */
			if (cbd->partial) {
				if (r < 0) {
					lua_tcp_push_error (cbd, strerror (errno));
				}
			}
			else {
				if (cbd->in->len > 0) {
					lua_tcp_push_data (cbd, cbd->in->str, cbd->in->len);
				}
				else {
					lua_tcp_push_error (cbd, "IO read error");
				}
			}

			lua_tcp_maybe_free (cbd);
		}
		else {
			if (cbd->partial) {
				lua_tcp_push_data (cbd, inbuf, r);
			}
			else {
				g_string_append_len (cbd->in, inbuf, r);

				if (cbd->stop_pattern) {
					slen = strlen (cbd->stop_pattern);

					if (cbd->in->len >= slen) {
						if (memcmp (cbd->stop_pattern, cbd->in->str +
								(cbd->in->len - slen), slen) == 0) {
							lua_tcp_push_data (cbd, cbd->in->str, cbd->in->len);
							lua_tcp_maybe_free (cbd);
						}
					}
				}
			}
		}
	}
	else if (what == EV_WRITE) {
		lua_tcp_write_helper (cbd);
	}
	else {
		lua_tcp_push_error (cbd, "IO timeout");
		lua_tcp_maybe_free (cbd);
	}
}

static gboolean
lua_tcp_make_connection (struct lua_tcp_cbdata *cbd)
{
	int fd;

	rspamd_inet_address_set_port (cbd->addr, cbd->port);
	fd = rspamd_inet_address_connect (cbd->addr, SOCK_STREAM, TRUE);

	if (fd == -1) {
		msg_info ("cannot connect to %s", rspamd_inet_address_to_string (cbd->addr));
		return FALSE;
	}
	cbd->fd = fd;

	event_set (&cbd->ev, cbd->fd, EV_WRITE, lua_tcp_handler, cbd);
	event_base_set (cbd->ev_base, &cbd->ev);
	event_add (&cbd->ev, &cbd->tv);

	return TRUE;
}

static void
lua_tcp_dns_handler (struct rdns_reply *reply, gpointer ud)
{
	struct lua_tcp_cbdata *cbd = (struct lua_tcp_cbdata *)ud;

	if (reply->code != RDNS_RC_NOERROR) {
		lua_tcp_push_error (cbd, "unable to resolve host");
		lua_tcp_maybe_free (cbd);
	}
	else {
		if (reply->entries->type == RDNS_REQUEST_A) {
			cbd->addr = rspamd_inet_address_new (AF_INET,
					&reply->entries->content.a.addr);
		}
		else if (reply->entries->type == RDNS_REQUEST_AAAA) {
			cbd->addr = rspamd_inet_address_new (AF_INET6,
					&reply->entries->content.aaa.addr);
		}

		rspamd_inet_address_set_port (cbd->addr, cbd->port);

		if (!lua_tcp_make_connection (cbd)) {
			lua_tcp_push_error (cbd, "unable to make connection to the host");
			lua_tcp_maybe_free (cbd);
		}
	}
}

static gboolean
lua_tcp_arg_toiovec (lua_State *L, gint pos, rspamd_mempool_t *pool,
		struct iovec *vec)
{
	struct rspamd_lua_text *t;
	gsize len;
	const gchar *str;

	if (lua_type (L, pos) == LUA_TUSERDATA) {
		t = lua_check_text (L, pos);

		if (t) {
			vec->iov_base = (void *)t->start;
			vec->iov_len = t->len;
		}
		else {
			return FALSE;
		}
	}
	else if (lua_type (L, pos) == LUA_TSTRING) {
		str = luaL_checklstring (L, pos, &len);
		vec->iov_base = rspamd_mempool_alloc (pool, len + 1);
		rspamd_strlcpy (vec->iov_base, str, len + 1);
		vec->iov_len = len;
	}
	else {
		return FALSE;
	}

	return TRUE;
}

/***
 * @function rspamd_tcp.request({params})
 * This function creates and sends TCP request to the specified host and port,
 * resolves hostname (if needed) and invokes continuation callback upon data received
 * from the remote peer. This function accepts table of arguments with the following
 * attributes
 *
 * - `task`: rspamd task objects (implies `pool`, `session`, `ev_base` and `resolver` arguments)
 * - `ev_base`: event base (if no task specified)
 * - `resolver`: DNS resolver (no task)
 * - `session`: events session (no task)
 * - `pool`: memory pool (no task)
 * - `host`: IP or name of the peer (required)
 * - `port`: remote port to use (required)
 * - `data`: a table of strings or `rspamd_text` objects that contains data pieces
 * - `callback`: continuation function (required)
 * - `timeout`: floating point value that specifies timeout for IO operations in seconds
 * - `partial`: boolean flag that specifies that callback should be called on any data portion received
 * - `stop_pattern`: stop reading on finding a certain pattern (e.g. \r\n.\r\n for smtp)
 * @return {boolean} true if request has been sent
 */
static gint
lua_tcp_request (lua_State *L)
{
	const gchar *host;
	gchar *stop_pattern = NULL;
	guint port;
	gint cbref, tp;
	struct event_base *ev_base;
	struct lua_tcp_cbdata *cbd;
	struct rspamd_dns_resolver *resolver;
	struct rspamd_async_session *session;
	struct rspamd_task *task = NULL;
	rspamd_mempool_t *pool;
	struct iovec *iov = NULL;
	guint niov = 0, total_out;
	gdouble timeout = default_tcp_timeout;
	gboolean partial = FALSE;

	if (lua_type (L, 1) == LUA_TTABLE) {
		lua_pushstring (L, "host");
		lua_gettable (L, -2);
		host = luaL_checkstring (L, -1);
		lua_pop (L, 1);

		lua_pushstring (L, "port");
		lua_gettable (L, -2);
		port = luaL_checknumber (L, -1);
		lua_pop (L, 1);

		lua_pushstring (L, "callback");
		lua_gettable (L, -2);
		if (host == NULL || lua_type (L, -1) != LUA_TFUNCTION) {
			lua_pop (L, 1);
			msg_err ("tcp request has bad params");
			lua_pushboolean (L, FALSE);
			return 1;
		}
		cbref = luaL_ref (L, LUA_REGISTRYINDEX);

		lua_pushstring (L, "task");
		lua_gettable (L, -2);
		if (lua_type (L, -1) == LUA_TUSERDATA) {
			task = lua_check_task (L, -1);
			ev_base = task->ev_base;
			resolver = task->resolver;
			session = task->s;
			pool = task->task_pool;
		}
		lua_pop (L, 1);

		if (task == NULL) {
			lua_pushstring (L, "ev_base");
			lua_gettable (L, -2);
			if (luaL_checkudata (L, -1, "rspamd{ev_base}")) {
				ev_base = *(struct event_base **)lua_touserdata (L, -1);
			}
			else {
				ev_base = NULL;
			}
			lua_pop (L, 1);

			lua_pushstring (L, "pool");
			lua_gettable (L, -2);
			if (luaL_checkudata (L, -1, "rspamd{mempool}")) {
				pool = *(rspamd_mempool_t **)lua_touserdata (L, -1);
			}
			else {
				pool = NULL;
			}
			lua_pop (L, 1);

			lua_pushstring (L, "resolver");
			lua_gettable (L, -2);
			if (luaL_checkudata (L, -1, "rspamd{resolver}")) {
				resolver = *(struct rspamd_dns_resolver **)lua_touserdata (L, -1);
			}
			else {
				resolver = lua_tcp_global_resolver (ev_base);
			}
			lua_pop (L, 1);

			lua_pushstring (L, "session");
			lua_gettable (L, -2);
			if (luaL_checkudata (L, -1, "rspamd{session}")) {
				session = *(struct rspamd_async_session **)lua_touserdata (L, -1);
			}
			else {
				session = NULL;
			}
			lua_pop (L, 1);
		}

		lua_pushstring (L, "timeout");
		lua_gettable (L, -2);
		if (lua_type (L, -1) == LUA_TNUMBER) {
			timeout = lua_tonumber (L, -1) * 1000.;
		}
		lua_pop (L, 1);

		lua_pushstring (L, "stop_pattern");
		lua_gettable (L, -2);
		if (lua_type (L, -1) == LUA_TSTRING) {
			stop_pattern = rspamd_mempool_strdup (pool, lua_tostring (L, -1));
		}
		lua_pop (L, 1);

		lua_pushstring (L, "partial");
		lua_gettable (L, -2);
		if (lua_type (L, -1) == LUA_TBOOLEAN) {
			partial = lua_toboolean (L, -1);
		}
		lua_pop (L, 1);

		if (pool == NULL) {
			lua_pop (L, 1);
			msg_err ("tcp request has no memory pool associated");
			lua_pushboolean (L, FALSE);
			return 1;
		}

		lua_pushstring (L, "data");
		lua_gettable (L, -2);
		total_out = 0;

		tp = lua_type (L, -1);
		if (tp == LUA_TSTRING || tp == LUA_TUSERDATA) {
			iov = rspamd_mempool_alloc (pool, sizeof (*iov));
			niov = 1;

			if (!lua_tcp_arg_toiovec (L, -1, pool, iov)) {
				lua_pop (L, 1);
				msg_err ("tcp request has bad data argument");
				lua_pushboolean (L, FALSE);
				return 1;
			}

			total_out = iov[0].iov_len;
		}
		else if (tp == LUA_TTABLE) {
			/* Count parts */
			lua_pushnil (L);
			while (lua_next (L, -2) != 0) {
				niov ++;
				lua_pop (L, 1);
			}

			iov = rspamd_mempool_alloc (pool, sizeof (*iov) * niov);
			lua_pushnil (L);
			niov = 0;

			while (lua_next (L, -2) != 0) {
				if (!lua_tcp_arg_toiovec (L, -1, pool, &iov[niov])) {
					lua_pop (L, 2);
					msg_err ("tcp request has bad data argument at pos %d", niov);
					lua_pushboolean (L, FALSE);
					return 1;
				}

				total_out += iov[niov].iov_len;
				niov ++;

				lua_pop (L, 1);
			}
		}

		lua_pop (L, 1);
	}
	else {
		msg_err ("tcp request has bad params");
		lua_pushboolean (L, FALSE);

		return 1;
	}

	cbd = g_slice_alloc0 (sizeof (*cbd));
	cbd->L = L;
	cbd->cbref = cbref;
	cbd->ev_base = ev_base;
	msec_to_tv (timeout, &cbd->tv);
	cbd->fd = -1;
	cbd->pool = pool;
	cbd->partial = partial;
	cbd->iov = iov;
	cbd->iovlen = niov;
	cbd->total = total_out;
	cbd->pos = 0;
	cbd->port = port;
	cbd->stop_pattern = stop_pattern;

	if (session) {
		cbd->session = session;
		rspamd_session_add_event (session,
				(event_finalizer_t)lua_tcp_fin,
				cbd,
				g_quark_from_static_string ("lua tcp"));
	}

	if (rspamd_parse_inet_address (&cbd->addr, host)) {
		rspamd_inet_address_set_port (cbd->addr, port);
		/* Host is numeric IP, no need to resolve */
		if (!lua_tcp_make_connection (cbd)) {
			lua_tcp_maybe_free (cbd);
			lua_pushboolean (L, FALSE);

			return 1;
		}
	}
	else {
		make_dns_request (resolver, session, NULL, lua_tcp_dns_handler, cbd,
				RDNS_REQUEST_A, host);
	}

	lua_pushboolean (L, TRUE);
	return 1;
}

static gint
lua_load_tcp (lua_State * L)
{
	lua_newtable (L);
	luaL_register (L, NULL, tcp_libf);

	return 1;
}

void
luaopen_tcp (lua_State * L)
{
	rspamd_lua_add_preload (L, "rspamd_tcp", lua_load_tcp);
}
eference Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
summaryrefslogtreecommitdiffstats
path: root/lib/l10n/sl.json
blob: 346c81521e7fe00ed725ae967a3803fc6ffd9db5 (plain)
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
{ "translations": {
    "Cannot write into \"config\" directory!" : "Mapa 'config' nima določenih ustreznih dovoljenj za pisanje!",
    "This can usually be fixed by giving the webserver write access to the config directory" : "Napako je mogoče odpraviti z dodelitvijo dovoljenja spletnemu strežniku za pisanje v nastavitveno mapo.",
    "See %s" : "Oglejte si %s",
    "Sample configuration detected" : "Zaznana je neustrezna preizkusna nastavitev",
    "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "V sistem je bila kopirana datoteka s vzorčnimi nastavitvami. To lahko vpliva na namestitev in zato možnost ni podprta. Pred spremembami datoteke config.php si natančno preberite dokumentacijo.",
    "%1$s and %2$s" : "%1$s in %2$s",
    "%1$s, %2$s and %3$s" : "%1$s, %2$s in %3$s",
    "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s in %4$s",
    "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s in %5$s",
    "Enterprise bundle" : "Poslovni paket",
    "Groupware bundle" : "Paket za skupinsko delo",
    "Social sharing bundle" : "Paket družbene izmenjave",
    "PHP %s or higher is required." : "Zahtevana je različica PHP %s ali višja.",
    "PHP with a version lower than %s is required." : "Zahtevana je različica PHP manj kot %s.",
    "%sbit or higher PHP required." : "Zahtevana je različica PHP %s ali višja.",
    "Following databases are supported: %s" : "Podprte so navedene podatkovne zbirke: %s",
    "The command line tool %s could not be found" : "Orodja ukazne vrstice %s ni mogoče najti",
    "The library %s is not available." : "Knjižnica %s ni na voljo.",
    "Following platforms are supported: %s" : "Podprta so okolja: %s",
    "Server version %s or higher is required." : "Zahtevana je različica strežnika %s ali višja.",
    "Server version %s or lower is required." : "Zahtevana je različica strežnika %s ali nižja.",
    "Authentication" : "Overitev",
    "Unknown filetype" : "Neznana vrsta datoteke",
    "Invalid image" : "Neveljavna slika",
    "Avatar image is not square" : "Slika podobe ni kvadratna",
    "today" : "danes",
    "tomorrow" : "jutri",
    "yesterday" : "včeraj",
    "_in %n day_::_in %n days_" : ["čez %n dan","čez %n dneva","čez %n dni","čez %n dni"],
    "_%n day ago_::_%n days ago_" : ["pred %n dnevom","pred %n dnevoma","pred %n dnevi","pred %n dnevi"],
    "next month" : "naslednji mesec",
    "last month" : "zadnji mesec",
    "_in %n month_::_in %n months_" : ["čez %n mesec","čez %n meseca","čez %n mesece","čez %n mesecev"],
    "_%n month ago_::_%n months ago_" : ["pred %n mesecem","pred %n mesecema","pred %n meseci","pred %n meseci"],
    "next year" : "naslednje leto",
    "last year" : "lansko leto",
    "_in %n year_::_in %n years_" : ["čez %n leto","čez %n leti","čez %n leta","čez %n let"],
    "_%n year ago_::_%n years ago_" : ["pred %n letom","pred %n letoma","pred %n leti","pred %n leti"],
    "_in %n hour_::_in %n hours_" : ["čez %n uro","čez %n uri","čez %n ure","čez %n ur"],
    "_%n hour ago_::_%n hours ago_" : ["pred %n uro","pred %n urama","pred %n urami","pred %n urami"],
    "_in %n minute_::_in %n minutes_" : ["čez %n minuto","čez %n minuti","čez %n minute","čez %n minut"],
    "_%n minute ago_::_%n minutes ago_" : ["pred %n minuto","pred %n minutama","pred %n minutami","pred %n minutami"],
    "in a few seconds" : "čez nekaj sekund",
    "seconds ago" : "pred nekaj sekundami",
    "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modul z ID: %s ne obstaja. Omogočite ga med nastavitvami, ali pa stopite v stik s skrbnikom sistema.",
    "File name is a reserved word" : "Ime datoteke je zadržana beseda",
    "File name contains at least one invalid character" : "Ime datoteke vsebuje vsaj en nedovoljen znak.",
    "File name is too long" : "Ime datoteke je predolgo",
    "Dot files are not allowed" : "Skrite datoteke niso dovoljene",
    "Empty filename is not allowed" : "Prazno polje imena datoteke ni dovoljeno.",
    "App \"%s\" cannot be installed because appinfo file cannot be read." : "Programa »%s« ni mogoče namestiti, ker datoteke appinfo ni mogoče brati.",
    "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Programa »%s« ni mogoče namestiti, ker ni združljiv z nameščeno različico strežnika.",
    "__language_name__" : "Slovenščina",
    "This is an automatically sent email, please do not reply." : "To sporočilo je samodejno poslano, nanj se nima smisla odzvati.",
    "Help" : "Pomoč",
    "Apps" : "Programi",
    "Settings" : "Nastavitve",
    "Log out" : "Odjava",
    "Users" : "Uporabniki",
    "Unknown user" : "Neznan uporabnik",
    "Overview" : "Splošni pregled",
    "Basic settings" : "Osnovne nastavitve",
    "Sharing" : "Souporaba",
    "Security" : "Varnost",
    "Groupware" : "Skupinsko delo",
    "Additional settings" : "Dodatne nastavitve",
    "Personal info" : "Osebni podatki",
    "Mobile & desktop" : "Mobilni in namizni dostop",
    "%s enter the database username and name." : "%s - vnos uporabniškega imena in imena podatkovne zbirke.",
    "%s enter the database username." : "%s - vnos uporabniškega imena podatkovne zbirke.",
    "%s enter the database name." : "%s - vnos imena podatkovne zbirke.",
    "%s you may not use dots in the database name" : "%s - v imenu podatkovne zbirke ni dovoljeno uporabljati pik.",
    "Oracle connection could not be established" : "Povezave s sistemom Oracle ni mogoče vzpostaviti.",
    "Oracle username and/or password not valid" : "Uporabniško ime ali geslo Oracle ni veljavno",
    "PostgreSQL username and/or password not valid" : "Uporabniško ime ali geslo PostgreSQL ni veljavno",
    "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Sistem Mac OS X ni podprt, zato %s v tem okolju ne bo deloval zanesljivo. Program uporabljate na lastno odgovornost! ",
    "For the best results, please consider using a GNU/Linux server instead." : "Za najbolj še rezultate je priporočljivo uporabljati strežnik GNU/Linux.",
    "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Videti je, da je dejavna seja %s zagnana v 32-bitnem okolju PHP in, da je v datoteki php.ini nastavljen open_basedir . Tako delovanje ni priporočljivo, saj se lahko pojavijo težave z datotekami, večjimi od 4GB.",
    "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Odstraniti je treba nastavitev open_basedir v datoteki php.ini ali pa preklopiti na 64-bitno okolje PHP.",
    "Set an admin username." : "Nastavi uporabniško ime skrbnika.",
    "Set an admin password." : "Nastavi skrbniško geslo.",
    "Can't create or write into the data directory %s" : "Ni mogoče zapisati podatkov v podatkovno mapo %s",
    "Invalid Federated Cloud ID" : "Neveljaven ID zveznega oblaka ownCloud",
    "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Souporaba ozadnjega programa %s mora vsebovati tudi vmesnik OCP\\Share_Backend",
    "Sharing backend %s not found" : "Ozadnjega programa %s za souporabo ni mogoče najti",
    "Sharing backend for %s not found" : "Ozadnjega programa za souporabo za %s ni mogoče najti",
    "Open »%s«" : "Odpri »%s«",
    "You are not allowed to share %s" : "Omogočanje souporabe %s brez ustreznih dovoljenj ni mogoče.",
    "Expiration date is in the past" : "Datum preteka je že mimo!",
    "Could not find category \"%s\"" : "Kategorije \"%s\" ni mogoče najti.",
    "Sunday" : "nedelja",
    "Monday" : "ponedeljek",
    "Tuesday" : "torek",
    "Wednesday" : "sreda",
    "Thursday" : "četrtek",
    "Friday" : "petek",
    "Saturday" : "sobota",
    "Sun." : "ned",
    "Mon." : "pon",
    "Tue." : "tor",
    "Wed." : "sre",
    "Thu." : "čet",
    "Fri." : "pet",
    "Sat." : "sob",
    "Su" : "ne",
    "Mo" : "po",
    "Tu" : "to",
    "We" : "sr",
    "Th" : "če",
    "Fr" : "pe",
    "Sa" : "so",
    "January" : "januar",
    "February" : "februar",
    "March" : "marec",
    "April" : "april",
    "May" : "maj",
    "June" : "junij",
    "July" : "julij",
    "August" : "avgust",
    "September" : "september",
    "October" : "oktober",
    "November" : "november",
    "December" : "december",
    "Jan." : "jan",
    "Feb." : "feb",
    "Mar." : "mar",
    "Apr." : "apr",
    "May." : "maj",
    "Jun." : "jun",
    "Jul." : "jul",
    "Aug." : "avg",
    "Sep." : "sep",
    "Oct." : "okt",
    "Nov." : "nov",
    "Dec." : "dec",
    "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V uporabniškem imenu je dovoljeno uporabiti le znake: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
    "A valid username must be provided" : "Navedeno mora biti veljavno uporabniško ime",
    "Username contains whitespace at the beginning or at the end" : "Uporabniško ime vsebuje presledni znak na začetku ali na koncu imena.",
    "Username must not consist of dots only" : "Uporabniško ime ne sme biti zgolj iz pik",
    "A valid password must be provided" : "Navedeno mora biti veljavno geslo",
    "The username is already being used" : "Vpisano uporabniško ime je že v uporabi",
    "User disabled" : "Uporabnik je onemogočen",
    "Login canceled by app" : "Program onemogoča prijavo",
    "a safe home for all your data" : "Varno okolje za vaše podatke!",
    "File is currently busy, please try again later" : "Datoteka je trenutno v uporabi. Poskusite znova kasneje.",
    "Can't read file" : "Datoteke ni mogoče prebrati.",
    "Application is not enabled" : "Program ni omogočen",
    "Authentication error" : "Napaka overjanja",
    "Token expired. Please reload page." : "Žeton je potekel. Stran je treba ponovno naložiti.",
    "No database drivers (sqlite, mysql, or postgresql) installed." : "Ni nameščenih programnikov podatkovnih zbirk (sqlite, mysql, ali postgresql).",
    "Cannot write into \"config\" directory" : "Mapa 'config' nima nastavljenih ustreznih dovoljenj za pisanje!",
    "Cannot write into \"apps\" directory" : "Mapa \"apps\" nima nastavljenih ustreznih dovoljenj za pisanje!",
    "Setting locale to %s failed" : "Nastavljanje jezikovnih določil na %s je spodletelo.",
    "Please install one of these locales on your system and restart your webserver." : "Namestiti je treba podporo za vsaj eno od navedenih jezikovnih določil v sistemu in nato ponovno zagnati spletni strežnik.",
    "Please ask your server administrator to install the module." : "Obvestite skrbnika strežnika, da je treba namestiti manjkajoč modul.",
    "PHP module %s not installed." : "Modul PHP %s ni nameščen.",
    "PHP setting \"%s\" is not set to \"%s\"." : "Nastavitev PHP \"%s\" ni nastavljena na \"%s\".",
    "Adjusting this setting in php.ini will make Nextcloud run again" : "Nastavitev tega parametra v <bold>php.ini</bold> bo vzpostavilo ponovno delovanje NextCloud",
    "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload je nastavljeno na \"%s\" namesto pričakovane vrednosti \"0\"",
    "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini" : "Za reštev te težave nastavi <code>mbstring.func_overload</code> na <code>0</code> v tvojem php.ini",
    "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 mora iti vsaj 2.7.0. Trenutno je nameščena %s.",
    "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik.",
    "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : " Izgleda, da je PHP nastavljen, da odreže znake v 'inline doc' blokih. To bo povzročilo, da nekateri osnovni moduli ne bodo dosegljivi.",
    "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Napako je najverjetneje povzročil predpomnilnik ali pospeševalnik, kot sta Zend OPcache ali eAccelerator.",
    "PHP modules have been installed, but they are still listed as missing?" : "Ali so bili moduli PHP nameščeni, pa so še vedno označeni kot manjkajoči?",
    "Please ask your server administrator to restart the web server." : "Obvestite skrbnika strežnika, da je treba ponovno zagnati spletni strežnik.",
    "PostgreSQL >= 9 required" : "Zahtevana je različica PostgreSQL >= 9.",
    "Please upgrade your database version" : "Posodobite različico podatkovne zbirke.",
    "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Spremenite dovoljenja mape na 0770 in s tem onemogočite branje vsebine drugim uporabnikom.",
    "Check the value of \"datadirectory\" in your configuration" : "V konfiguraciji preverite nastavitev \"datadirectory\"",
    "Could not obtain lock type %d on \"%s\"." : "Ni mogoče pridobiti zaklepa %d na \"%s\".",
    "Storage unauthorized. %s" : "Dostop do shrambe ni overjen. %s",
    "Storage incomplete configuration. %s" : "Nepopolna nastavitev shrambe. %s",
    "Storage connection error. %s" : "Napaka povezave do shrambe. %s",
    "Storage is temporarily not available" : "Shramba trenutno ni na voljo",
    "Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s",
    "Create" : "Ustvari",
    "Change" : "Spremeni",
    "Delete" : "Izbriši",
    "Sharing %s failed, because the backend does not allow shares from type %i" : "Omogočanje souporabe %s je spodletelo, ker ozadnji program ne dopušča souporabe vrste %i.",
    "Sharing %s failed, because the file does not exist" : "Souporaba %s je spodletela, ker ta datoteka ne obstaja",
    "Sharing %s failed, because you can not share with yourself" : "Nastavitev %s souporabe je spodletela, ker souporaba s samim seboj ni mogoča.",
    "You need to provide a password to create a public link, only protected links are allowed" : "Navesti je treba geslo za ustvarjanje javne povezave, saj so dovoljene le zaščitene.",
    "Sharing %s failed, because sharing with links is not allowed" : "Nastavljanje souporabe %s je spodletelo, ker souporaba preko povezave ni dovoljena.",
    "Not allowed to create a federated share with the same user" : "Ni dovoljeno ustvariti souporabe zveznega oblaka z istim uporabnikom",
    "Cannot set expiration date. Shares cannot expire later than %s after they have been shared" : "Ni mogoče določiti datuma preteka. Ni dovoljeno, da so mape ali datoteke, dodeljene v souporabo, v souporabi po %s.",
    "Cannot set expiration date. Expiration date is in the past" : "Ni mogoče nastaviti datuma preteka. Ta datum je že preteklost.",
    "Sharing failed, because the user %s is the original sharer" : "Nastavitev souporabe je spodletela, ker je uporabnik %s souporabo izvorno nastavil.",
    "Sharing %s failed, because resharing is not allowed" : "Nastavljanje souporabe %s je spodletelo, ker nadaljnje omogočanje souporabe ni dovoljeno.",
    "Sharing %s failed, because the file could not be found in the file cache" : "Nastavljanje souporabe %s je spodletelo, ker v predpomnilniku zahtevana datoteka ne obstaja."
},"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"
}